装个蒜。学习下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,虽然缺乏了不少很是好的特性且更低效率。但这能让咱们瞥见其内部工做过程。

(已下载相关文件,百度云盘)。

相关文章
相关标签/搜索