GCD學習
- GCD其他方法
- dispatch_semaphore (信號量)
- **什么是信號量**
- dispatch_semaphore主要作用
- dispatch_semaphore主要作用
- 異步轉同步
- 設置一個最大開辟的線程數
- 加鎖機制
- dispatch_time_t 兩種形式
- GCD一次性代碼(只執行一次)
- dispatch_barrier_async/sync柵欄方法
GCD其他方法
前面對GCD進行了簡單的學習,這里筆者對一些容易遺忘和重要的方法再次學習。
dispatch_semaphore (信號量)
什么是信號量
引用學長的解釋
以一個停車場的運作為例。簡單起見,假設停車場只有三個車位,一開始三個車位都是空的。這時如果同時來了五輛車,看 門人允許其中三輛直接進入,然后放下車攔,剩下的車則必須在入口等待,此后來的車也都不得不在入口處等待。這時,有一輛車離開停車場,看門人得知后,打開 車攔,放入外面的一輛進去,如果又離開兩輛,則又可以放入兩輛,如此往復。在這個停車場系統中,車位是公共資源,每輛車好比一個線程,看門人起的就是信號量的作用。
信號量主要用作同步鎖,用于控制GCD最大并發數
主要涉及以下三個函數:
// 創建信號量,value:初始信號量數 如果小于0則會返回NULL
dispatch_semaphore_create(long value); // 發送信號量是的信號量+1
dispatch_semaphore_signal(dispatch_semaphore_t deem);//可以使總信號量減 1,信號總量小于 0 時就會一直等待(阻塞所在線程),否則就可以正常執行。
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
注意:信號量的使用前提是:想清楚你需要處理哪個線程等待(阻塞),又要哪個線程繼續執行,然后使用信號量。
dispatch_semaphore主要作用
dispatch_semaphore主要作用
- 保持線程同步,將異步執行任務轉換為同步執行任務
- 保證線程安全,為線程加鎖
異步轉同步
- (void)cjl_testSemaphore1{dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);//創建異步隊列dispatch_queue_t queue = dispatch_get_global_queue(0, 0);dispatch_async(queue, ^{sleep(1);NSLog(@"執行任務A");//信號量+1dispatch_semaphore_signal(semaphore);});dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);dispatch_async(queue, ^{sleep(1);NSLog(@"執行任務B");//信號量+1,相當于解鎖dispatch_semaphore_signal(semaphore);});//當當前的信號量值為0時候會阻塞線,如果大于0的話,信號量-1,不阻塞線程dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);dispatch_async(queue, ^{sleep(1);NSLog(@"執行任務C");//信號量+1,相當于解鎖dispatch_semaphore_signal(semaphore);});}
多次運行的結果都是A,B,C順序執行,讓A,B,C異步執行變成同步執行,dispatch_semaphore相當于加鎖效果
設置一個最大開辟的線程數
- (void)cjl_testSemaphore{
//設置最大開辟的線程數為3dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);//創建一個并發隊列dispatch_queue_t queue = dispatch_get_global_queue(0, 0);//開啟的是10個異步的操作,通過信號量,讓10個異步的最多3個m,剩下的同步等待for (NSInteger i = 0; i < 10; i++) {dispatch_async(queue, ^{//當信號量為0時候,阻塞當前線程dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);NSLog(@"執行任務 %ld", i);sleep(2);NSLog(@"完成當前任務 %ld", i);//釋放信號dispatch_semaphore_signal(semaphore);});}
}
但如果sleep時間變成1,則會出現以下結果:
這是因為出現了優先級反轉的問題。
先介紹什么是優先級反轉。
在程序執行時多個任務可能會對同—份數據產生 競爭’因此任務會使用鎖來保護共享數據°假設現在有3個任務A、B`C’它們的優先 級為A>B>C任務C在運行時持有一把鎖’然后它被高優先級的任務A搶占了(任 務C的鎖沒有被釋放)。此時任務A恰巧也想申請任務C持有的鎖’但是申請失敗,因 此進人阻塞狀態等待任務C放鎖。此時’任務B、C都處于可以運行的狀態,由于任務 B的優先級高于C,因此B優先運行°綜合觀察該情況’就會發現任務B好像優先級高 于任務A’先于任務A執行。
信號量調度是公平隊列(FIFO/不考慮 QoS)+ 系統線程調度是根據優先級(QoS)來的,兩者機制不一致,所以可能造成高優先級線程因為信號量資源被低優先級線程占用而“被阻塞”。
加鎖機制
- 當你的信號設置為1的時候就相當于加鎖
- (void)viewDidLoad {[super viewDidLoad];NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程NSLog(@"semaphore---begin");semaphoreLock = dispatch_semaphore_create(1);self.ticketCount = 50;// queue1 代表北京火車票售賣窗口dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);// queue2 代表上海火車票售賣窗口dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);__weak typeof(self) weakSelf = self;dispatch_async(queue1, ^{[weakSelf saleTicketSafe];});dispatch_async(queue2, ^{[weakSelf saleTicketSafe];});
}- (void)saleTicketSafe {while (1) {// 相當于加鎖dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);if (self.ticketCount > 0) { //如果還有票,繼續售賣self.ticketSurplusCount--;NSLog(@"%@", [NSString stringWithFormat:@"剩余票數:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);[NSThread sleepForTimeInterval:0.2];} else { //如果已賣完,關閉售票窗口NSLog(@"所有火車票均已售完"); // 相當于解鎖dispatch_semaphore_signal(semaphoreLock);break;} // 相當于解鎖dispatch_semaphore_signal(semaphoreLock);}
}
dispatch_time_t 兩種形式
- 基于
dispatch_time
函數,表示從當前時間開始的一個時間點
dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);
使用:
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(60 * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^{NSLog(@"dispatch_time");
});
時間單位:
#define NSEC_PER_SEC 1000000000ull 1秒
#define NSEC_PER_MSEC 1000000ull 1毫秒
#define USEC_PER_SEC 1000000ull 1秒
#define NSEC_PER_USEC 1000ull 1微秒
時間點:
#define DISPATCH_TIME_NOW (0ull) 0->現在
#define DISPATCH_TIME_FOREVER (~0ull) -1->永遠
dispatch_time_t定義
typedef uint64_t dispatch_time_t; unsigned long long 64位無符號長整形
GCD一次性代碼(只執行一次)
使用dispatch_once
方法能保證某段代碼在程序運行過程中只執被執行1次,即使在多線程的環境下,該方法也可以保證建成安全。之前在創建單例模式時就接觸過該方法。
- (void)once {static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{// 只執行 1 次的代碼(這里面默認是線程安全的)});}
dispatch_barrier_async/sync柵欄方法
在訪問數據庫或者文件的時候,我們可以使用Serial Dispatch Queue可避免數據競爭問題。即多讀單寫
- (void)cjl_testBarrier{/*dispatch_barrier_sync & dispatch_barrier_async應用場景:同步鎖等柵欄前追加到隊列中的任務執行完畢后,再將柵欄后的任務追加到隊列中。簡而言之,就是先執行柵欄前任務,再執行柵欄任務,最后執行柵欄后任務- dispatch_barrier_async:前面的任務執行完畢才會來到這里- dispatch_barrier_sync:作用相同,但是這個會堵塞線程,影響后面的任務執行- dispatch_barrier_async可以控制隊列中任務的執行順序,- 而dispatch_barrier_sync不僅阻塞了隊列的執行,也阻塞了線程的執行(盡量少用)*/[self cjl_testBarrier1];[self cjl_testBarrier2];
}
- (void)cjl_testBarrier1{//串行隊列使用柵欄函數dispatch_queue_t queue = dispatch_queue_create("CJL", DISPATCH_QUEUE_SERIAL);NSLog(@"開始 - %@", [NSThread currentThread]);dispatch_async(queue, ^{sleep(2);NSLog(@"延遲2s的任務1 - %@", [NSThread currentThread]);});NSLog(@"第一次結束 - %@", [NSThread currentThread]);//柵欄函數的作用是將隊列中的任務進行分組,所以我們只要關注任務1、任務2dispatch_barrier_async(queue, ^{NSLog(@"------------柵欄任務------------%@", [NSThread currentThread]);});NSLog(@"柵欄結束 - %@", [NSThread currentThread]);dispatch_async(queue, ^{sleep(2);NSLog(@"延遲2s的任務2 - %@", [NSThread currentThread]);});NSLog(@"第二次結束 - %@", [NSThread currentThread]);
}
- (void)cjl_testBarrier2{//并發隊列使用柵欄函數dispatch_queue_t queue = dispatch_queue_create("CJL", DISPATCH_QUEUE_CONCURRENT);NSLog(@"開始 - %@", [NSThread currentThread]);dispatch_async(queue, ^{sleep(2);NSLog(@"延遲2s的任務1 - %@", [NSThread currentThread]);});NSLog(@"第一次結束 - %@", [NSThread currentThread]);//由于并發隊列異步執行任務是亂序執行完畢的,所以使用柵欄函數可以很好的控制隊列內任務執行的順序dispatch_barrier_async(queue, ^{NSLog(@"------------柵欄任務------------%@", [NSThread currentThread]);});NSLog(@"柵欄結束 - %@", [NSThread currentThread]);dispatch_async(queue, ^{sleep(2);NSLog(@"延遲2s的任務2 - %@", [NSThread currentThread]);});NSLog(@"第二次結束 - %@", [NSThread currentThread]);
}
重點觀察并發隊列中使用柵欄函數。會在柵欄前的任務完成后,才執行柵欄后的任務。
dispatch_barrier_sync
- 會阻塞當前線程,看打印結果可知,“start”一定在dispatch_barrier_sync函數前執行,“end”一定在dispatch_barrier_sync函數后執行
- dispatch_barrier_sync 函數添加的block,在當前線程執行
- 會將傳入dispatch_barrier_sync函數的線程myQueue中的任務,通過dispatch_barrier_sync函數位置把前后分隔開。
dispatch_barrier_async
- 不會阻塞當前線程,“start”,“end”與dispatch_barrier_async方法是同步執行的,執行順序不定。
- dispatch_barrier_sync函數添加的block,在新開的線程執行
- 會將傳入dispatch_barrier_async函數的線程myQueue中的任務,通過dispatch_barrier_async函數位置把前后分隔開。
這也是為什么柵欄結束會出現在柵欄任務之前。