前言:
在研究《iOS 性能監控(二)—— 主線程卡頓監控》中,
發現有一些GCD
信號量的知識以前沒有好好梳理過。
故本篇用來梳理一下GCD
中信號量dispatch_semaphore_t
相關的知識。git
信號量(Semaphore
)是多線程環境下的一種保護設施,能夠用來保證兩個或多個關鍵代碼不被併發調用。github
在進入一個關鍵代碼段以前,線程必須獲取一個信號量。一旦執行完畢,該線程就會釋放信號量。等待下一個信號量被髮送,線程才能繼續獲取到新信號量並再次執行關鍵代碼段。objective-c
signal
與等信號wait
應該要一一對應)舉個例子:
一個停車場,只能容下5輛車。這時候,來了6輛車。只有前5輛能進去。第6輛車等待,當有一輛車離開停車場時,才能進入。
這裏,
想進停車場 —— 建立信號,
當前有車位 ,領卡進場 —— 發信號,
當前無車位,排隊等卡 —— 等信號,
離開停車場 —— 銷燬信號。多線程
一般來講,信號量有4
種操做。併發
initialize
/create
)signal
/post
)wait
/suspend
)destroy
)而在咱們iOS開發中,想使用信號量,首先想到的就是GCD
中的dispatch_semphore_t
。異步
dispatch_semaphore_create(long value)
dispatch_semaphore_create(long value); //!< 建立信號量
複製代碼
參數 | 說明 |
---|---|
value | 信號量的初始數量(>=0)。 注意:傳遞一個小於零的值將會返回NULL。 |
若是 value > 0
,就至關於建立了個信號量,並同時發出value個信號。
若是 value = 0
,就至關於單純僅僅建立了個信號量,還沒發信號。
若是 value < 0
,直接failure,返回一個NULL。async
dispatch_semaphore_signal(dispatch_semaphore_t dsema);
dispatch_semaphore_signal(dispatch_semaphore_t dsema); //!< 發送信號量
複製代碼
參數 | 說明 |
---|---|
dispatch_semaphore_t | 傳入所要發送信號的信號量。 dispatch_semaphore_t的信號計數+1。 |
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout); //!< 等待信號量
複製代碼
參數 | 說明 |
---|---|
dispatch_semaphore_t | 傳入所要等待信號的信號量。dispatch_semaphore_t 的信號計數-1。 |
dispatch_time_t | 超時等待時間。超過該時間就返回非0,並會直接往下執行。 也能夠設置爲 DISPATCH_TIME_FOREVER ,永久等待。 |
返回值 | 說明 |
---|---|
Int | 成功收到信號返回0,超時未收到返回非0。 |
使用信號量使「異步」線程完成「同步」操做。oop
即便是在多線程併發的場景,也能夠經過控制信號量來保證操做的同步。post
舉個例子:一般,咱們要實現異步線程完成同步操做。有兩種作法:性能
這種狀況只會開啓一條子線程,並按順序執行串行操做。
dispatch_queue_t queue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"111:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"222:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"333:%@",[NSThread currentThread]);
});
複製代碼
這種方式有些缺陷:
第一: 由於是異步操做,因此會開啓一個新的子線程, 同時又是串行隊列,因此只會開啓一條子線程進行同步操做。 喪失了多線程的優點。
第二: 須要寫在一個方法裏去作, 而實際開發中,可能異步分佈在各個方法中,但同時又想串行去執行。
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"任務1:%@",[NSThread currentThread]);
dispatch_semaphore_signal(sem);
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"任務2:%@",[NSThread currentThread]);
dispatch_semaphore_signal(sem);
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"任務3:%@",[NSThread currentThread]);
});
複製代碼
固然,這裏只是個例子。在實際應用中, 發送信號(signal
),與等待信號(wait
)每每是成對出現的。 同時,一般是分開在不一樣的方法裏調用。
例如,在《iOS 性能監控(二)—— 主線程卡頓監控》當中:
監控主線程的CommonModes發生變化時,會發送信號。
同時會開啓一條子線程的loop持續監聽CommonModes的變化,等待信號。
在某些條件下,超時等待時,就說明主線程當前處於卡頓狀態。
保存當前的主線程方法調用堆棧就達到了監控的目的。
PS:詳細的實現,可在QiLagMonitor源碼中查看。