iOS GCD信號量dispatch_semaphore_t

前言:
在研究《iOS 性能監控(二)—— 主線程卡頓監控》中,
發現有一些GCD信號量的知識以前沒有好好梳理過。
故本篇用來梳理一下GCD中信號量dispatch_semaphore_t相關的知識。
git


1、信號量(Semaphore)簡介

信號量(Semaphore)是多線程環境下的一種保護設施,能夠用來保證兩個或多個關鍵代碼不被併發調用。github

在進入一個關鍵代碼段以前,線程必須獲取一個信號量。一旦執行完畢,該線程就會釋放信號量。等待下一個信號量被髮送,線程才能繼續獲取到新信號量並再次執行關鍵代碼段。objective-c

  • 要求:線程進入關鍵代碼段前,必需要獲取到一個信號量。(發信號signal與等信號wait應該要一一對應)
  • 做用:保證關鍵代碼段不被併發調用。

舉個例子:
一個停車場,只能容下5輛車。這時候,來了6輛車。只有前5輛能進去。第6輛車等待,當有一輛車離開停車場時,才能進入。
這裏,
想進停車場 —— 建立信號,
當前有車位 ,領卡進場 —— 發信號,
當前無車位,排隊等卡 —— 等信號,
離開停車場 —— 銷燬信號。
多線程

一般來講,信號量有4種操做。併發

  1. 初始化信號(initialize/create
  2. 發信號(signal/post
  3. 等信號(wait/suspend
  4. 釋放信號(destroy

2、GCD信號量(dispatch_semphore_t)

而在咱們iOS開發中,想使用信號量,首先想到的就是GCD中的dispatch_semphore_t異步

1. 建立信號量

  • 方法:dispatch_semaphore_create(long value)
dispatch_semaphore_create(long value); //!< 建立信號量
複製代碼
  • 說明:
參數 說明
value 信號量的初始數量(>=0)。
注意:傳遞一個小於零的值將會返回NULL。

若是 value > 0,就至關於建立了個信號量,並同時發出value個信號。
若是 value = 0,就至關於單純僅僅建立了個信號量,還沒發信號。
若是 value < 0,直接failure,返回一個NULL。async

2. 發送信號量

  • 方法:dispatch_semaphore_signal(dispatch_semaphore_t dsema);
dispatch_semaphore_signal(dispatch_semaphore_t dsema); //!< 發送信號量
複製代碼
  • 說明:
參數 說明
dispatch_semaphore_t 傳入所要發送信號的信號量。
dispatch_semaphore_t的信號計數+1。

3. 等待信號量

  • 方法: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。

3、信號量的應用

使用信號量使「異步」線程完成「同步」操做。oop

即便是在多線程併發的場景,也能夠經過控制信號量來保證操做的同步。post

舉個例子:一般,咱們要實現異步線程完成同步操做。有兩種作法:性能

1. 第一種:使用串行隊列+異步操做。

這種狀況只會開啓一條子線程,並按順序執行串行操做。

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]);
    });
複製代碼

這種方式有些缺陷:

第一: 由於是異步操做,因此會開啓一個新的子線程, 同時又是串行隊列,因此只會開啓一條子線程進行同步操做。 喪失了多線程的優點。

第二: 須要寫在一個方法裏去作, 而實際開發中,可能異步分佈在各個方法中,但同時又想串行去執行。

2. 第二種:使用信號量,控制多線程下的同步操做。

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源碼中查看。

相關文章
相關標籤/搜索