在iOS多線程開發(fā)環(huán)境中墨叛,我們往往會用信號量Semaphore解決一些特別的問題,它不僅高效而且也易于理解尝盼。這里我總結(jié)了加鎖吞滞、異步返回、控制線程并發(fā)數(shù)這三個用途盾沫,下面通過一些例子進行解釋裁赠。
一、加鎖
代碼形式:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
for (int i = 0; i < 10000; i++) {
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
//臨界區(qū)疮跑,即待加鎖的代碼區(qū)域
dispatch_semaphore_signal(semaphore);
});
}
在進行多線程任務之前组贺,首先創(chuàng)建一個計數(shù)為1的信號量,這樣可以保證同一時刻只有一個線程在訪問臨界區(qū)祖娘,dispatch_semaphore_create()
會為我們完成。
在要訪問臨界區(qū)之前,通過 dispatch_semaphore_wait()
函數(shù)渐苏,我們可以在信號量為 0 時掀潮,讓臨界區(qū)外的線程進入等待狀態(tài)。
在這里琼富,當?shù)谝粭l線程訪問臨界區(qū)時仪吧,信號量計數(shù)為初始值1,
dispatch_semaphore_wait()
函數(shù)判斷到計數(shù)大于0鞠眉,于是將計數(shù)減1薯鼠,從而線程允許訪問臨界區(qū)。其它線程因為信號量等于0械蹋,就在臨界區(qū)外等待出皇。
在第一條線程訪問完臨界區(qū)后,這條線程需要發(fā)出一個信號哗戈,來表明我已經(jīng)用完臨界區(qū)的資源了郊艘,下個正在等待的線程可以去訪問了。
dispatch_semaphore_signal()
會將信號量計數(shù)加1唯咬,就好像發(fā)出了一個信號一樣纱注,下個在臨界區(qū)前等待的線程會去接收它。接收到了信號的線程判斷到信號量計數(shù)大于零了胆胰,于是訪問臨界區(qū)狞贱。
通過重復這個過程,所有線程都會安全地訪問一遍臨界區(qū)蜀涨。
貼一段YYKit中的簡單的加鎖代碼:
- (instancetype)init {
self = [super init];
_lock = dispatch_semaphore_create(1);
return self;
}
- (NSURL *)imageURL {
dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER);
NSURL *imageURL = _imageURL;
dispatch_semaphore_signal(_lock);
return imageURL;
}
二瞎嬉、異步任務,同步返回
- (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) {
//task賦值勉盅,代碼有點長佑颇,就不貼了
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return tasks;
}
上面是 AFNetworking 的一段代碼,我且稱之為異步返回草娜。這段代碼的功能是通過異步的請求取得鍵路徑為 keyPath 的任務數(shù)組 tasks挑胸,然后返回它。這個方法雖然是異步的宰闰,但是執(zhí)行時間較短茬贵。
碰到這種情況,我們肯定最先想到的是用代碼塊 block 或者代理 delegate 來實現(xiàn)移袍,然后我們就得去聲明一個代理解藻,寫一個協(xié)議方法,或者寫一個帶有一個參數(shù)的代碼塊葡盗,這里AFNetworking巧妙地通過信號量解決了螟左。
我們跟之前的加鎖對比,可以發(fā)現(xiàn),信號量在創(chuàng)建時計數(shù)是0胶背,
dispatch_semaphore_signal() 函數(shù)在 dispatch_semaphore_wait() 函數(shù)之前巷嚣。
AFNetworking 把 dispatch_semaphore_wait() 函數(shù)放在返回語句之前,同時信號量計數(shù)初始為0钳吟,是為了讓線程在 tasks 有值之前一直等待廷粒。獲取 tasks 的異步操作結(jié)束之后,這時候 tasks 賦值好了红且,于是通過 dispatch_semaphore_signal() 函數(shù)發(fā)出信號坝茎,外面的線程就知道不用等待,可以返回 tasks 了暇番。
其實信號量進行了隱式的線程間通信嗤放,仔細想想,信號量本身是否線程安全呢奔誓?
三斤吐、控制線程并發(fā)數(shù)
在 GCD 中,dispatch_async()
異步操作可以產(chǎn)生新的線程厨喂,但是方法本身沒辦法限制線程的最大并發(fā)數(shù)和措,線程的創(chuàng)建和銷毀是由 GCD 底層管理的。
了解 NSOperationQueue 的同學肯定知道蜕煌,通過 maxConcurrentOperationCount 屬性可以設置它的最大并發(fā)數(shù)派阱。那么在GCD中,對應的解決方法就是使用信號量斜纪。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(5);
for (int i = 0; i < 1000; ++i) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
//多線程代碼
dispatch_semaphore_signal(semaphore);
});
}
其實跟加鎖代碼非常相似贫母,區(qū)別在于,在初始化信號量時盒刚,將計數(shù)賦值為最大并發(fā)數(shù)腺劣。在應用場景上,限制線程并發(fā)數(shù)是為了性能考慮因块,而加鎖是為了安全而考慮橘原。