iOS多線程編程之GCD詳解(二)完結

前言

上一篇詳細介紹了介紹了GCD中的常用API,
iOS多線程編程之GCD詳解(一)
考慮到篇幅問題,這里繼續介紹另外的兩個API。

1.Dispatch Semaphore 信號量

dispatch_semaphore_t 信號量本質上是一種鎖。
關于iOS中各種鎖和性能比較可以看下yykit作者的這篇博文,戳這里
不再安全的 OSSpinLock

下面我們看下信號量的使用:
dispatch_semaphore_t 的作用之一解決資源搶奪問題
之前提過,對于數據存儲類似數據庫,非原子性可變字典和可變數組等多線程下不安全的操作,可以使用同步隊列保證線程安全,那么在并發隊列中,可以使用信號量來解決資源搶奪問題

  //全局隊列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //創建一個信號量,初始值為1
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1) ;
    //創建可變數組
    NSMutableArray *array = [[NSMutableArray alloc] init];
    for(int i = 0; i< 1000; ++i) {
        dispatch_async(queue, ^{
            
            //這里會一直等待,直到信號量大于等于1
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER) ;
            
            //執行到這里,消費一個信號量
            NSLog(@"%@",[NSThread currentThread]);
            [array addObject:[NSNumber numberWithInt:i]];
            
            //這里增加一個信號量
            dispatch_semaphore_signal(semaphore);
        });
    }

代碼解讀一下:
dispatch_semaphore_create(1) 創建了值為1信號量
dispatch_semaphore_wait ,如果信號量的值大于等于1,那么,信號量值減1,然后向下執行,如果信號量值為0,一直等待。直到大于等于1的時候,率先進入等待狀態的異步隊列率先執行
dispatch_semaphore_signal 信號量值加1

形象比喻一下:
一群人排隊去銀行辦業務,銀行初始只有一個窗口,第一個人辦業務的時候,可用窗口就變成0個了,這個人辦完業務,可用窗口加1,就變成1個了。

實際這種效果和加鎖的本質一致。
dispatch_semaphore_t 的另外一個作用就是可以控制線程并發數量,之前我們提過,iOS7之后系統自動開辟的線程數量可以多達60-70,而GCD中并沒有提供控制線程數量的API,NSOperation中可以設置最大線程數。

下面我們使用信號量來實現一下線程數量控制:

    //線程并發數限制
    static dispatch_semaphore_t limitSemaphore;
    //控制專用隊列
    static dispatch_queue_t serialQueue;
    
    //單例創建
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
      //設置最大線程并發數為5
        limitCount = dispatch_semaphore_create(5);
        serialQueue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
    });
    
    dispatch_async(serialQueue, ^{
        //信號量>=1繼續執行,否則等待
        dispatch_semaphore_wait(limitSemaphore, DISPATCH_TIME_FOREVER);
        dispatch_async(queue, ^{
            //這里執行一些任務
            NSLog(@"%@",[NSThread currentThread]);
            //在該工作線程執行完成后釋放信號量
            dispatch_semaphore_signal(limitSemaphore);
        });
    });

2.dispatch source

dispatch source 是一組不常用的GCD API。是BSD系內核慣有功能kqueue的包裝。kqueue的介紹可以看下這個kqueue wikipedia
簡單來說,dispatch source是一個監視某些類型事件的對象。它支持所有kqueue所支持的事件以及mach(mach介紹可以看這里mach wikipedia)端口、內建計時器支持和用戶事件,CPU負荷占用小,資源占用小。

dispatch source聯結
聯結的流程:在任一線程上調用dispatch_source_merge_data 這個函數后,會執行 Dispatch Source 事先定義好的句柄(可以簡單理解句柄就是block )(是不是有點通知,回調的味道哈)
下面直接上代碼:

//全局隊列
 dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//創建source
    dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
//定義source的句柄
    dispatch_source_set_event_handler(source, ^{
//調用一次dispatch_source_merge_data會調用這個句柄
        NSLog(@"%lu",dispatch_source_get_data(source));
    });
    //默認source是suspend的,需要resume生效
    dispatch_resume(source);
    
//遍歷10次
    dispatch_apply(10, globalQueue, ^(size_t index) {
        // merge data
        dispatch_source_merge_data(source, 1);
    });

這段程序簡單邏輯:調用dispatch_source_merge_data 會觸發實現定義好的事件

dispatch_source_set_event_handler(source, ^{
//調用一次dispatch_source_merge_data會調用這個句柄
        NSLog(@"%lu",dispatch_source_get_data(source));
    });

dispatch_source_create 函數參數DISPATCH_SOURCE_TYPE_DATA_ADD 累加
當注冊系統事件的時候,有時候系統還沒來得及通知應用程序,這個時候,系統會累計傳遞過來的值
DISPATCH_SOURCE_TYPE_DATA_OR 邏輯或處理累計傳遞過來的值
其他:
DISPATCH_SOURCE_TYPE_MACH_SEND MACH端口發送
DISPATCH_SOURCE_TYPE_MACH_RECV MACH端口接收
DISPATCH_SOURCE_TYPE_PROC 監測進程相關事件
DISPATCH_SOURCE_TYPE_READ 可讀取文件映像
DISPATCH_SOURCE_TYPE_SIGNAL 接收信號
DISPATCH_SOURCE_TYPE_TIMER 定時器
DISPATCH_SOURCE_TYPE_VNODE 文件系統變更
DISPATCH_SOURCE_TYPE_WRITE 可寫入文件映像

注冊事件處理程序通知,如果系統沒來的及通知應用程序時候事件發生多次,這些事件會合并為一個事件(是不是類似于TCP協議中的nagle算法)。iOS開發者通常不會用到這種功能。但對于底層,這種處理方式會很高效.

簡單流程總結:創建一個源,自定義累計方式,可以是and也可以是Or,自定義源也需要一個隊列用來處理響應塊,可以是主隊列,也可以是并發隊列。

在同一時間,只有一個響應塊被分派。處理方法沒執行完畢,另一個事件發生,事件以指定方式(ADD或者OR)進行累積。通過合并,保證了在高負載下穩定執行。
累計值通過 dispatch_source_get_data 獲取。每次響應執行事件,這個值會被重置
dispatch_source_merge_data 發送一個事件
默認創建出來的source是掛起狀態的,需要調用dispatch_resume 才可生效

除了高效的自定義一個source處理自定事件之外,我們也可以使用dispatch_source 來定義一個定時器,iOS開發中常用的定時器有NSTimerCADisplayLink 兩種
NSTimer 受到runloop的狀態影響精度
CADisplayLink 則和屏幕刷新度幀數一致
dispatch_source 作為定時器精度很高,是系統級別的源

demo如下:

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//定時器作為屬性創建
     self.timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    //開始時間
    dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, 3.0 * NSEC_PER_SEC);
    //間隔時間
    uint64_t interval = 2.0 * NSEC_PER_SEC;
  //設置時間
    dispatch_source_set_timer( self.timerSource start, interval, 0);

    //設置回調
    dispatch_source_set_event_handler( self.timerSource, ^{
        //處理事件
    });

    //啟動定時器
    dispatch_resume( self.timerSource);

總結一下:
本文主要介紹了
dispatch_semaphore_t ,本質是一種底層鎖,性能較高,可以用來解決多線程資源競爭,控制線程并發數。
dispatch source 最大的優勢是聯結,通過合并事件的方式,高效的處理事件分派,可以自定義source用來處理高負載應用場景響應。dispatch source 可以作為高精度,系統源層級的定時器,在需要高精度應用場景下可以選用這種更加接近底層的定時器。

寫了兩篇博客來詳解了下GCD,主要是對自我基礎的一個總結。之前已經有很多寫的很好的GCD文章。關于信號量和dispatch_source還有興趣深入了解的可以閱讀下官方文檔。
推薦下閱讀猿神的博客
Parse源碼淺析系列(一)---Parse的底層多線程處理思路:GCD高級用法

參考書籍:
Objective-C高級編程

前篇:
iOS多線程編程之GCD詳解(一)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容