iOS中的多執行緒(關於GCD訊號量)
highlight: a11y-dark theme: cyanosis
「這是我參與11月更文挑戰的第10天,活動詳情檢視:2021最後一次更文挑戰」
GCD 訊號量
什麼是 Dispatch Semaphore
GCD 中的訊號量是指 Dispatch Semaphore,是持有計數的訊號。類似於過高速路收費站的欄杆。可以通過時,開啟欄杆,不可以通過時,關閉欄杆。在 Dispatch Semaphore 中,使用計數來完成這個功能,計數小於 0 時等待,不可通過。計數為 0 或大於 0 時,計數減 1 且不等待,可通過。
Dispatch Semaphore 的方法
-
建立訊號量 建立一個
dispatch_semaphore_
型別的訊號量,並且建立的時候需要指定訊號量的大小,當傳遞的值小於0,訊號量將初始化失敗返回NULL
js dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
-
傳送訊號量 傳送一個訊號,會對訊號量進行加 1
js
dispatch_semaphore_signal(semaphore);
- 等待訊號量
該函式會對訊號量進行減 1。如果減 1 後訊號量小於 0(即減1前訊號量值為0),那麼該函式就會一直等待,也就是不返回(相當於阻塞當前執行緒),直到該函式等待的訊號量的值大於等於 1,該函式會對訊號量的值進行減1操作,然後返回。
js
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
通常等待訊號量和傳送訊號量的函式是成對出現的。併發執行任務時候,在當前任務執行之前,用 dispatch_semaphore_wait
函式進行等待(阻塞),直到上一個任務執行完畢後且通過 dispatch_semaphore_signal
函式傳送訊號量(使訊號量的值加1),dispatch_semaphore_wait
函式收到訊號量之後判斷訊號量的值大於等於1,會再對訊號量的值減1,然後當前任務可以執行,執行完畢當前任務後,再通過 dispatch_semaphore_signal
函式傳送訊號量(使訊號量的值加1),通知執行下一個任務......如此一來,通過訊號量,就達到了併發佇列中的任務同步執行的要求
用訊號量機制使非同步執行緒完成同步操作
在併發佇列中的任務,由非同步執行緒執行的順序是不確定的,兩個任務分別由兩個執行緒執行,很難控制哪個任務先執行完,哪個任務後執行完。但有時候確實有這樣的需求:兩個任務雖然是非同步的,但仍需要同步執行。這時候,GCD訊號量就可以大顯身手了。
-
非同步函式+併發佇列 實現同步操作
我們知道非同步函式 + 序列佇列實現任務同步執行更加簡單。不過非同步函式 + 序列佇列的弊端也是非常明顯的:因為是非同步函式,所以系統會開啟新(子)執行緒,又因為是序列佇列,所以系統只會開啟一個子執行緒。這就導致了所有的任務都是在這個子執行緒中同步的一個一個執行。喪失了併發執行的可能性。雖然可以完成任務,但是卻沒有充分發揮CPU多核(多執行緒)的優勢
示例: ```js - (void)touchesBegan:(NSSet
)touches withEvent:(UIEvent )event{ dispatch_queue_t queue = dispatch_queue_create("com.gcd.serialQueue", DISPATCH_QUEUE_SERIAL); dispatch_async(queue, ^{ NSLog(@"任務1:%@",[NSThread currentThread]); }); dispatch_async(queue, ^{ NSLog(@"任務2:%@",[NSThread currentThread]); }); dispatch_async(queue, ^{ NSLog(@"任務3:%@",[NSThread currentThread]); });
}
``` log:(三個任務的執行順序永遠是任務1、任務2、任務3,且永遠是在同一個子執行緒被執行) -
用GCD的訊號量來實現非同步執行緒同步操作
使用訊號量實現非同步執行緒同步操作時,雖然任務是一個接一個被同步(說同步並不準確)執行的,但因為是在併發佇列,並不是所有的任務都是在同一個執行緒執行的(所以說同步並不準確)。log中綠框中的任務3是線上程8中被執行的,而任務1和任務2是線上程3中被執行的。這有別於非同步函式+序列佇列的方式(非同步函式+ 序列佇列的方式中,所有的任務都是在同一個新執行緒被序列執行的)
```js - (void)touchesBegan:(NSSet
)touches withEvent:(UIEvent )event{ dispatch_semaphore_t sem = dispatch_semaphore_create(0); NSLog(@"1、任務1加入併發佇列"); dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"任務1:%@",[NSThread currentThread]); NSLog(@"3、任務1進行訊號量加1"); dispatch_semaphore_signal(sem); }); NSLog(@"2、任務1進行訊號量減1"); dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); NSLog(@"4、任務2加入併發佇列"); dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"任務2:%@",[NSThread currentThread]); NSLog(@"6、任務2進行訊號量加1"); dispatch_semaphore_signal(sem); }); NSLog(@"5、任務2進行訊號量減1"); dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); NSLog(@"7、任務3加入併發佇列"); dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"任務3:%@",[NSThread currentThread]); });
} ``` log:(點選事件3次的log,去除了執行順的log)
log:(單次點選事件的log,包含執行順序)
Dispatch Semaphore 的使用
訊號量的使用前提是:想清楚需要處理哪個執行緒阻塞,需要哪個執行緒繼續執行,然後使用訊號量
-
保持執行緒同步,將非同步執行任務轉換為同步執行任務
示例: ```js NSLog(@"currentThread---%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//建立一個 Semaphore, 並初始化訊號的總量 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block int number = 0;
//非同步執行 dispatch_async(queue, ^{ for (NSInteger i = 0; i<3; i++) { NSLog(@"非同步執行----%@",[NSThread currentThread]); } number = 100;
//傳送一個訊號,讓訊號總量加 1 dispatch_semaphore_signal(semaphore);
});
NSLog(@"非同步執行已新增併發佇列");
//總訊號量減 1 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"執行完畢,number = %d",number); ``` log:
通過log梳理執行順序:
1、semaphore 初始化建立訊號量為 0
2、非同步執行加入佇列後,不做等待,接著執行dispatch_semaphore_wait
,這時訊號量由0變為-1,當前執行緒進入等待狀態
3、非同步執行任務,通過dispatch_semaphore_signal
方法訊號量由-1變為0,當前被阻塞的執行緒恢復繼續執行
4、最後輸出:執行完畢,number = 100,這樣就實現了執行緒同步,將非同步執行任務轉換為同步執行任務 -
保證執行緒安全,為執行緒加鎖
示例: ```js - (void)viewDidLoad{ semaphore = dispatch_semaphore_create(1);
self.ticketSurplusCount = 5; dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_CONCURRENT); __weak typeof(self) weakSelf = self; dispatch_async(queue, ^{ [weakSelf saleTicketSafe]; }); dispatch_async(queue, ^{ [weakSelf saleTicketSafe]; });
}
- (void)saleTicket{ while (1) { // 相當於加鎖 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); if (self.ticketSurplusCount > 0) { self.ticketSurplusCount--; NSLog(@"%@", [NSString stringWithFormat:@"剩餘票數:%ld 售票員:%@", (long)self.ticketSurplusCount, [NSThread currentThread]]); [NSThread sleepForTimeInterval:0.2]; } else { NSLog(@"票賣完了"); // 相當於解鎖 dispatch_semaphore_signal(semaphore); break; } // 相當於解鎖 dispatch_semaphore_signal(semaphore); } } ``` log:
- iOS-螢幕適配(基礎概念)
- Web 開發模式
- Node.js - 在 express 中啟用 cors 跨域資源共享
- 簡單的HTTP協議
- iOS中的Storyboard
- iOS中載入xib
- iOS中自定義view的封裝
- iOS中的事件
- iOS中的定時器(GCD定時器)
- iOS中的定時器(CADisplayLink)
- iOS中的執行緒鎖(關於NSRecursiveLock)
- iOS中的執行緒鎖(關於NSConditionLock)
- iOS中的執行緒鎖(執行緒鎖的相關概念)
- iOS中的多執行緒(多執行緒的競爭)
- iOS中的多執行緒(關於NSOperationQueue)
- iOS中的多執行緒(關於NSOperation)
- iOS中的多執行緒(關於GCD訊號量)
- iOS中的多執行緒(關於GCD的其他方法)
- iOS中的多執行緒(關於GCD 的佇列組)
- iOS中的多執行緒(關於GCD的佇列和任務)