dispatch queue的真髓:能串行,能並行,能同步,能異步以及共享同一個線程池。前端
接口:後端
GCD是基於C語言的APT。雖然最新的系統版本中GCD對象已經轉成了Objective-C對象,但API仍保持純C接口(加了block擴展)。這對實現底層接口是好事,GCD提供了出色而簡單的接口。數組
Objective-C類名稱爲MADispatchQueue,包含四個調用方法:安全
1. 獲取全局共享隊列的方法。GCD有多個不一樣優先級的全局隊列,出於簡單考慮,咱們在實現中保留一個。併發
2. 串行和並行隊列的初始化函數。異步
3. 異步分發調用函數
4. 同步分發調用oop
接口聲明:spa
@interface MADispatchQueue:NSObject線程
+ (MADispatchQueue *)globalQueue;
- (id)initSerial:(BOOL)serial;
- (void)dispatchAsync: (dispatch_block_t)block;
@end
接下來的目標就是實現這些方法的功能。
線程池接口:
隊列後面的線程池接口更簡單。它將真正執行提交的任務。隊列負責在合適的時間把已入隊的任務提交給它。
線程池只作一件事:投遞任務並運行。對應的,一個接口只有一個方法:
@interface MAThreadPool:NSObject
- (void)addBlock:(dispatch_block_t)block;
@end
因爲這是核心,咱們先實現它。
線程池實現
首先看實例變量。線程池能被多個內部線程或多個外部線程訪問,所以須要線程安全。而在可能的狀況下,GCD會使用原子操做,而我在這裏以一種之前比較流行的方式-加鎖。我須要知道鎖處於等待和鎖相關的信號,而不只僅強制其互斥,所以我使用NSCondition而不是NSLock。若是你不熟悉,NSCondition本質上仍是鎖,只是添加了一個條件變量:
NSCondition *_lock;
想要知道何時增長工做線程,我要知道線程池裏的線程數,有多少線程正被佔用以及所能擁有的最大線程數:
NSUInteger _threadCount;
NSUInteger _activeThreadCount;
NSUInteger _threadCountLimit;
最後,得有一個NSMutableArray類型的block列表模擬一個隊列,從隊列後端添加block,從隊列前端刪除:
NSMutableArray *_blocks;
初始化函數很簡單。初始化鎖和block數組,隨便設置一個最大線程數好比128:
- (id)init{
if((self = [super init])){
_lock = [NSCondition alloc] init];
_blocks = [NSMutableArray alloc] init];
_threadCountLimit = 128;
}
return self;
}
工做線程運行了一個簡單的無限循環。只要block數組爲空,它將一直等待。一旦有block加入,它將被從數組中取出並執行。同時將活動線程數加1,完成後活動線程數減1;
- (void)worderThreadLoop: (id)ignore{ //首先要獲取鎖,注意須要在循環開始前得到。至於緣由,等寫道循環結束時你就會明白。 [_lock Lock]; //無限循環開始 while (1) { while ([_blocks count] == 0) { [_lock wait]; } /* 注意:這裏是內循環結束而非if判斷。緣由是因爲虛假喚醒。簡單來講就是wait在沒有信號通知的狀況下也有可能返回,目前爲止,條件檢測的正確方式是當wait返回時從新進行條件檢測。 */ //一旦有隊列中有block,取出: dispatch_block_t block = [_blocks firstObject]; [_blocks removeObjectAtIndex:0]; //活動線程計數加,表示有新線程正在處理任務: _activeThreadCount++; //如今執行block,咱們先得釋放鎖,否則代碼併發執行時會出現死鎖: [_lock unlock]; //安全釋放鎖後,執行block block(); //block執行完畢,活動線程計數減1.該操做必須在鎖內作,以免競態條件,最後是循環結束: [_lock lock]; _activeThreadCount--; } }
//下面是addBlock: - (void)addBlock: (dispatch_block_t)block{ //這裏惟一須要作的是得到鎖: [_lock lock]; //添加一個新的block到block隊列 [_blocks addObject: block]; //若是有一個空閒的工做線程去執行這個bock的話,這裏什麼都不須要作。若是沒有足夠的工做線程去處理等待的block,而工做線程數也沒超限,則咱們須要建立一個新線程: NSUInteger idleThreads = _threadCount = _activeThreadCount; if ([_blocks count] > idleThreads && _threadCount < _threadCountLimit) { [NSThread detachNewThreadSelector:@selector(workerThreadLoop:) toTarget:self withObject:nil]; _threadCount++; } // 一切準備就緒,因爲空閒線程都在休眠,喚醒它: [_lock signal]; // 最後釋放鎖: [_lock unlock]; // 線程池能在達到預設的最大線程數數前建立工做線程,以處理對應的block。如今以此爲基礎實現隊列。 /* 隊列實現 和線程池同樣,隊列使用鎖保護其內容。和線程池不一樣的是,它不須要等待鎖,也不須要信號觸發,僅僅是簡單互斥便可,所以採用NSLock; */ NSLock *lock; // 和線程池同樣,它把pending block存在NSMutableArray裏。 NSMutableArray *_pendingBlocks; // 標誌是串行仍是並行隊列; BOOL _serial; // 若是是串行隊列,還須要標識當前是否有線程正在運行: BOOL _serialRunning; // 並行隊列裏有無線程都同樣處理,因此無需關注。 // 全局隊列是一個全局變量,共享線程池也同樣。它們都在+initialize裏建立: static MADispatchQueue *gGlobalQueue; static MAThreadPool *gThreadPool; } + (void)initialize{ if (self == [MADispatchQueue class]) { gGlobalQueue = [[MADispatchQueue alloc] initSerial: NO]; gThreadPool = [[MAThreadPool alloc] init]; } } //因爲+initialize裏已經初始化了, +globalQueue只需返回該變量。 + (MADispatchQueue *)globalQueue { return gGlobalQueue; } //這裏所作的事情和dispatch_once是同樣的,可是實現GCD API的時候使用GCD API有點自欺欺人,即便代碼不同。 //初始化一個隊列:初始化lock 和pending Blocks,設置_serial變量: + (MADispatchQueue *)globalQueue { return gGlobalQueue; } //這裏所作的事情和dispatch_once是同樣的,可是實現GCD API的時候使用GCD API有點自欺欺人,即便代碼不同。 //初始化一個隊列:初始化lock 和pending Blocks,設置_serial變量: - (id)initSerial: (BOOL)serial { if ((self = [super init])) { _lock = [[NSLock alloc] init]; _pendingBlocks = [[NSMutableArray alloc] init]; _serial = serial; } return self; } //實現剩下的公有API前,咱們需先實現一個底層方法用於給線程分發一個block,而後繼續調用本身去處理另外一個block: - (void)dispatchOneBlock { // 整個生命週期所作的是在線程池上運行block,分發代碼以下: [gThreadPool addBlock: ^{ // 而後取隊列中的第一個block,顯然這須要在鎖內完成,以免出現問題: [_lock lock]; dispatch_block_t block = [_pendingBlocks firstObject]; [_pendingBlocks removeObjectAtIndex: 0]; [_lock unlock]; // 取到了block又釋放了鎖,block接下來能夠安全地在後臺線程執行了: block(); // 若是是並行執行的話就不須要再作啥了。若是是串行執行,還須要如下操做: if(_serial) { // 串行隊列裏將會積累別的block,但不能執行,直到先前的block完成。block完成後,dispatchOneBlock 接下來會看是否還有其餘的block被添加到隊列裏面。如有,它調用本身去處理下一個block。若無,則把隊列的運行狀態置爲NO: [_lock lock]; if([_pendingBlocks count] > 0) { [self dispatchOneBlock]; } else { _serialRunning = NO; } [_lock unlock]; } }]; } //用以上方法來實現dispatchAsync:就很是容易了。添加block到pending block隊列,合適的時候設置狀態並調用dispatchOneBlock: - (void)dispatchAsync: (dispatch_block_t)block { [_lock lock]; [_pendingBlocks addObject: block]; // 若是串行隊列空閒,設置隊列狀態爲運行並調用dispatchOneBlock 進行處理。 if(_serial && !_serialRunning) { _serialRunning = YES; [self dispatchOneBlock]; // 若是隊列是並行的,直接調用dispatchOneBlock。因爲多個block能並行執行,因此這樣能保證即便有其餘block正在運行,新的block也能當即執行。 } else if (!_serial) { [self dispatchOneBlock]; } // 若是串行隊列已經在運行,則不須要另外作處理。由於block執行完成後對dispatchOneBlock 的調用最終會調用加入到隊列的block。接着釋放鎖: [_lock unlock]; } //對於 dispatchSync: GCD的處理更巧妙,它是直接在調用線程上執行block,以防止其餘block在隊列上執行(若是是串行隊列)。在此咱們不用作如此聰明的處理,咱們僅僅是對dispatchAsync:進行封裝,讓其一直等待直到block執行完成。 //它使用局部NSCondition進行處理,另外使用一個done變量來指示block什麼時候完成: - (void)dispatchSync: (dispatch_block_t)block { NSCondition *condition = [[NSCondition alloc] init]; __block BOOL done = NO; // 下面是異步分發block。block裏面調用傳入的block,而後設置done的值,給condition發信號 [self dispatchAsync: ^{ block(); [condition lock]; done = YES; [condition signal]; [condition unlock]; }]; // 在調用線程裏面,等待信號done ,而後返回 [condition lock]; while (!done) { [condition wait]; } [condition unlock]; }
結論:全局線程池可使用block隊列和智能產生的線程實現。使用一個共享全局線程池,就能構建一個能提供基本的串行/並行,同步/異步功能的dispatch queue。這樣就重建了一個簡單的GCD,雖然缺乏了不少很是好的特性且更低效率。但這能讓咱們瞥見其內部工做過程。
(已下載相關文件,百度雲盤)。