iOS GCD (四) dispatch_semaphore 訊號量
iOS GCD (一) 任務+佇列 基礎組合
iOS GCD (二 ) dispatch_group 佇列組
iOS GCD(三) dispatch_barrier_async 柵欄方法
iOS GCD (四) dispatch_semaphore 訊號量
iOS GCD(五) 死鎖案例分析
iOS GCD(六)執行緒加鎖
GCD 中的訊號量是指 Dispatch Semaphore,是持有計數的訊號。類似於過高速路收費站的欄杆。可以通過時,開啟欄杆,不可以通過時,關閉欄杆。在 Dispatch Semaphore 中,使用計數來完成這個功能,計數為0時等待,不可通過。計數為1或大於1時,計數減1且不等待,可通過。
Dispatch Semaphore 提供了三個函式。
1.dispatch_semaphore_create:建立一個Semaphore並初始化訊號的總量
2.dispatch_semaphore_signal:傳送一個訊號,讓訊號總量加1
3.dispatch_semaphore_wait:可以使總訊號量減1,當訊號總量為0時就會一直等待(阻塞所線上程),否則就可以正常執行。
注意:訊號量的使用前提是:想清楚你需要處理哪個執行緒等待(阻塞),又要哪個執行緒繼續執行,然後使用訊號量。
Dispatch Semaphore 在實際開發中主要用於:
保持執行緒同步,將非同步執行任務轉換為同步執行任務
保證執行緒安全,為執行緒加鎖
Dispatch Semaphore 執行緒同步
我們在開發中,會遇到這樣的需求:非同步執行耗時任務,並使用非同步執行的結果進行一些額外的操作。換句話說,相當於,將將非同步執行任務轉換為同步執行任務。比如說:AFNetworking 中 AFURLSessionManager.m 裡面的 tasksForKeyPath: 方法。通過引入訊號量的方式,等待非同步執行任務結果,獲取到 tasks,然後再返回該 tasks。
- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
__block NSArray *tasks = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
tasks = dataTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
tasks = uploadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
tasks = downloadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
}
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return tasks;
}
下面,我們來利用 Dispatch Semaphore 實現執行緒同步,將非同步執行任務轉換為同步執行任務。
/**
* semaphore 執行緒同步
*/
- (void)semaphoreSync {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 列印當前執行緒
NSLog(@"semaphore---begin");
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block int number = 0;
dispatch_async(queue, ^{
// 追加任務1
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 列印當前執行緒
number = 100;
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"semaphore---end,number = %zd",number);
}
輸出結果:
2018-02-23 22:22:26.521665+0800 YSC-GCD-demo[20642:5246341] currentThread---<NSThread: 0x60400006bc80>{number = 1, name = main}
2018-02-23 22:22:26.521869+0800 YSC-GCD-demo[20642:5246341] semaphore---begin
2018-02-23 22:22:28.526841+0800 YSC-GCD-demo[20642:5246638] 1---<NSThread: 0x600000272300>{number = 3, name = (null)}
2018-02-23 22:22:28.527030+0800 YSC-GCD-demo[20642:5246341] semaphore---end,number = 100
從 Dispatch Semaphore 實現執行緒同步的程式碼可以看到:
semaphore---end 是在執行完 number = 100; 之後才列印的。而且輸出結果 number 為 100。
這是因為非同步執行不會做任何等待,可以繼續執行任務。非同步執行將任務1追加到佇列之後,不做等待,接著執行dispatch_semaphore_wait方法。此時 semaphore == 0,當前執行緒進入等待狀態。然後,非同步任務1開始執行。任務1執行到dispatch_semaphore_signal之後,總訊號量,此時 semaphore == 1,dispatch_semaphore_wait方法使總訊號量減1,正在被阻塞的執行緒(主執行緒)恢復繼續執行。最後列印semaphore---end,number = 100。這樣就實現了執行緒同步,將非同步執行任務轉換為同步執行任務。
Dispatch Semaphore 執行緒安全和執行緒同步(為執行緒加鎖)
執行緒安全:如果你的程式碼所在的程式中有多個執行緒在同時執行,而這些執行緒可能會同時執行這段程式碼。如果每次執行結果和單執行緒執行的結果是一樣的,而且其他的變數的值也和預期的是一樣的,就是執行緒安全的。
若每個執行緒中對全域性變數、靜態變數只有讀操作,而無寫操作,一般來說,這個全域性變數是執行緒安全的;若有多個執行緒同時執行寫操作(更改變數),一般都需要考慮執行緒同步,否則的話就可能影響執行緒安全。
執行緒同步:可理解為執行緒 A 和 執行緒 B 一塊配合,A 執行到一定程度時要依靠執行緒 B 的某個結果,於是停下來,示意 B 執行;B 依言執行,再將結果給 A;A 再繼續操作。
舉個簡單例子就是:兩個人在一起聊天。兩個人不能同時說話,避免聽不清(操作衝突)。等一個人說完(一個執行緒結束操作),另一個再說(另一個執行緒再開始操作)。
下面,我們模擬火車票售賣的方式,實現 NSThread 執行緒安全和解決執行緒同步問題。
場景:總共有50張火車票,有兩個售賣火車票的視窗,一個是北京火車票售賣視窗,另一個是上海火車票售賣視窗。兩個視窗同時售賣火車票,賣完為止。
1.非執行緒安全(不使用 semaphore)
先來看看不考慮執行緒安全的程式碼
/**
* 非執行緒安全:不使用 semaphore
* 初始化火車票數量、賣票視窗(非執行緒安全)、並開始賣票
*/
- (void)initTicketStatusNotSave {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 列印當前執行緒
NSLog(@"semaphore---begin");
self.ticketSurplusCount = 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 saleTicketNotSafe];
});
dispatch_async(queue2, ^{
[weakSelf saleTicketNotSafe];
});
}
/**
* 售賣火車票(非執行緒安全)
*/
- (void)saleTicketNotSafe {
while (1) {
if (self.ticketSurplusCount > 0) { //如果還有票,繼續售賣
self.ticketSurplusCount--;
NSLog(@"%@", [NSString stringWithFormat:@"剩餘票數:%d 視窗:%@", self.ticketSurplusCount, [NSThread currentThread]]);
[NSThread sleepForTimeInterval:0.2];
} else { //如果已賣完,關閉售票視窗
NSLog(@"所有火車票均已售完");
break;
}
}
}
輸出結果(部分):
2018-02-23 22:25:35.789072+0800 YSC-GCD-demo[20712:5258914] currentThread---<NSThread: 0x604000068880>{number = 1, name = main}
2018-02-23 22:25:35.789260+0800 YSC-GCD-demo[20712:5258914] semaphore---begin
2018-02-23 22:25:35.789641+0800 YSC-GCD-demo[20712:5259176] 剩餘票數:48 視窗:<NSThread: 0x60000027db80>{number = 3, name = (null)}
2018-02-23 22:25:35.789646+0800 YSC-GCD-demo[20712:5259175] 剩餘票數:49 視窗:<NSThread: 0x60000027e740>{number = 4, name = (null)}
2018-02-23 22:25:35.994113+0800 YSC-GCD-demo[20712:5259175] 剩餘票數:47 視窗:<NSThread: 0x60000027e740>{number = 4, name = (null)}
2018-02-23 22:25:35.994129+0800 YSC-GCD-demo[20712:5259176] 剩餘票數:46 視窗:<NSThread: 0x60000027db80>{number = 3, name = (null)}
2018-02-23 22:25:36.198993+0800 YSC-GCD-demo[20712:5259176] 剩餘票數:45 視窗:<NSThread: 0x60000027db80>{number = 3, name = (null)}
可以看到在不考慮執行緒安全,不使用 semaphore 的情況下,得到票數是錯亂的,這樣顯然不符合我們的需求,所以我們需要考慮執行緒安全問題。
2. 執行緒安全(使用 semaphore 加鎖)
考慮執行緒安全的程式碼:
/**
* 執行緒安全:使用 semaphore 加鎖
* 初始化火車票數量、賣票視窗(執行緒安全)、並開始賣票
*/
- (void)initTicketStatusSave {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 列印當前執行緒
NSLog(@"semaphore---begin");
semaphoreLock = dispatch_semaphore_create(1);
self.ticketSurplusCount = 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.ticketSurplusCount > 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);
}
}
輸出結果為:
2018-02-23 22:32:19.814232+0800 YSC-GCD-demo[20862:5290531] currentThread---<NSThread: 0x6000000783c0>{number = 1, name = main}
2018-02-23 22:32:19.814412+0800 YSC-GCD-demo[20862:5290531] semaphore---begin
2018-02-23 22:32:19.814837+0800 YSC-GCD-demo[20862:5290687] 剩餘票數:49 視窗:<NSThread: 0x6040002709c0>{number = 3, name = (null)}
2018-02-23 22:32:20.017745+0800 YSC-GCD-demo[20862:5290689] 剩餘票數:48 視窗:<NSThread: 0x60000046c640>{number = 4, name = (null)}
2018-02-23 22:32:20.222039+0800 YSC-GCD-demo[20862:5290687] 剩餘票數:47 視窗:<NSThread: 0x6040002709c0>{number = 3, name = (null)}
......
2018-02-23 22:32:29.024817+0800 YSC-GCD-demo[20862:5290689] 剩餘票數:4 視窗:<NSThread: 0x60000046c640>{number = 4, name = (null)}
2018-02-23 22:32:29.230110+0800 YSC-GCD-demo[20862:5290687] 剩餘票數:3 視窗:<NSThread: 0x6040002709c0>{number = 3, name = (null)}
2018-02-23 22:32:29.433615+0800 YSC-GCD-demo[20862:5290689] 剩餘票數:2 視窗:<NSThread: 0x60000046c640>{number = 4, name = (null)}
2018-02-23 22:32:29.637572+0800 YSC-GCD-demo[20862:5290687] 剩餘票數:1 視窗:<NSThread: 0x6040002709c0>{number = 3, name = (null)}
2018-02-23 22:32:29.840234+0800 YSC-GCD-demo[20862:5290689] 剩餘票數:0 視窗:<NSThread: 0x60000046c640>{number = 4, name = (null)}
2018-02-23 22:32:30.044960+0800 YSC-GCD-demo[20862:5290687] 所有火車票均已售完
2018-02-23 22:32:30.045260+0800 YSC-GCD-demo[20862:5290689] 所有火車票均已售完
可以看出,在考慮了執行緒安全的情況下,使用 dispatch_semaphore 機制之後,得到的票數是正確的,沒有出現混亂的情況。我們也就解決了多個執行緒同步的問題。
相關文章
- GCD(四) dispatch_semaphoreGC
- iOS開發 GCD訊號量實現AFNetworking的順序請求iOSGC
- iOS GCD詳解iOSGC
- iOS GCD執行緒之間的通訊iOSGC執行緒
- iOS GCD入門和GCD對CPU多核的使用iOSGC
- iOS 多執行緒之GCDiOS執行緒GC
- iOS多執行緒GCD篇iOS執行緒GC
- IOS多執行緒之(GCD)iOS執行緒GC
- POSIX 訊號量
- 程式間通訊——POSIX 有名訊號量與無名訊號量
- STM32的UCOS訊號量和互斥訊號量
- iOS 中的 GCD 實現詳解iOSGC
- iOS使用GCD實現一個TimeriOSGC
- iOS多執行緒:GCD詳解iOS執行緒GC
- iOS刨根問底-深入理解GCDiOSGC
- iOS底層GCD (技術總結)iOSGC
- django的訊號量Django
- liteos訊號量(八)
- iOS多執行緒開發—GCD (一)iOS執行緒GC
- iOS多執行緒(Pthread、NSThread、GCD、NSOperation)iOS執行緒threadGC
- 程序間通訊(4)-訊號量
- iOS Mach異常和signal訊號iOSMac
- 訊號量的使用 ManualResetEvent
- 10. Semaphore ||(訊號量)
- iOS 多執行緒:『GCD』詳盡總結iOS執行緒GC
- iOS-簡單易用的GCD計時器iOSGC
- Python執行緒專題5:訊號量與有邊界的訊號量Python執行緒
- system -v 訊號量的使用
- python學習之訊號量Python
- Semaphore訊號量原始碼解析原始碼
- GCD Inside: GCD 宏GCIDE
- iOS-GCD常用函式和柵欄函式iOSGC函式
- 原始碼分析:Semaphore之訊號量原始碼
- Linux中訊號量的實現Linux
- Linux中的System V訊號量Linux
- Linux 程式間通訊之System V 訊號量Linux
- 任務與佇列 iOS之多執行緒GCD(一)佇列iOS執行緒GC
- iOS 編寫高質量Objective-C程式碼(四)iOSObjectC程式