裝個蒜。學習下dispatch queue

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,雖然缺乏了不少很是好的特性且更低效率。但這能讓咱們瞥見其內部工做過程。

(已下載相關文件,百度雲盤)。

相關文章
相關標籤/搜索