前言
上一篇詳細介紹了介紹了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開發中常用的定時器有NSTimer
和CADisplayLink
兩種
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高級編程