dispatch queue的真髓:能串行,能并行,能同步,能异步以及共享同一个线程池。

接口:

GCD是基于C语言的APT。虽然最新的系统版本中GCD对象已经转成了Objective-C对象,但API仍保持纯C接口(加了block扩展)。这对实现底层接口是好事,GCD提供了出色而简单的接口。

Objective-C类名称为MADispatchQueue,包含四个调用方法:

1. 获取全局共享队列的方法。GCD有多个不同优先级的全局队列,出于简单考虑,我们在实现中保留一个。

2. 串行和并行队列的初始化函数。

3. 异步分发调用

4. 同步分发调用

接口声明:

@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 () {
while ([_blocks count] == ) {
[_lock wait];
}
/*
注意:这里是内循环结束而非if判断。原因是由于虚假唤醒。简单来说就是wait在没有信号通知的情况下也有可能返回,目前为止,条件检测的正确方式是当wait返回时重新进行条件检测。
*/
//一旦有队列中有block,取出:
dispatch_block_t block = [_blocks firstObject];
[_blocks removeObjectAtIndex:];
//活动线程计数加,表示有新线程正在处理任务:
_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: ];
[_lock unlock];
// 取到了block又释放了锁,block接下来可以安全地在后台线程执行了:
block();
// 如果是并行执行的话就不需要再做啥了。如果是串行执行,还需要以下操作:
if(_serial) {
// 串行队列里将会积累别的block,但不能执行,直到先前的block完成。block完成后,dispatchOneBlock 接下来会看是否还有其他的block被添加到队列里面。若有,它调用自己去处理下一个block。若无,则把队列的运行状态置为NO:
[_lock lock];
if([_pendingBlocks count] > ) {
[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,虽然缺少了很多非常好的特性且更低效率。但这能让我们瞥见其内部工作过程。

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

装个蒜。学习下dispatch queue的更多相关文章

  1. GCD 学习(三)Main&Global Dispatch Queue

    摘录自:http://zhuyanfeng.com/archives/3066 Main Dispatch Queue是在主线程中执行任务的Dispatch Queue.因为主线程只有1个,所以Mai ...

  2. GCD 学习(二)dispatch_queue_create创建Dispatch Queue

    摘录于: http://zhuyanfeng.com/archives/3042 dispatch_queue_create 用于创建用户线程队列.可以创建Serial/Concurrent Disp ...

  3. NSThread 、NSRunLoop 和 Dispatch Queue

     iOS多线程编程中,NSOperation和NSOperationQueue无疑是最常用的,它们能满足绝大部分情况下的线程操作.但在完成一些特殊的任务时,我们还是要使用的NSThread和NSRun ...

  4. GCD系列 之(一):基本概念和Dispatch Queue

    参考学习https://www.dreamingwish.com/article/grand-central-dispatch-basic-1.html系列文章,貌似也是翻译自他处的.觉得非常完整,就 ...

  5. Blocks与Dispatch Queue的使用

    block是什么block是一个C level的语法以及运行时的一个特性,和标准C中的函数(函数指针)类似.用于回调函数的地方.两个对象间的通讯.实现轻量级的“代理”. blocks和C语言函数指针的 ...

  6. IOS开发 GCD介绍: 基本概念和Dispatch Queue

    iOS的三种多线程技术 1.NSThread 每个NSThread对象对应一个线程,量级较轻(真正的多线程) 2.以下两点是苹果专门开发的“并发”技术,使得程序员可以不再去关心线程的具体使用问题 ØN ...

  7. GCD之dispatch queue

    GCD之dispatch queue iOS中多线程编程工具主要有: NSThread NSOperation GCD 这三种方法都简单易用,各有千秋.但无疑GCD是最有诱惑力的,因为其本身是appl ...

  8. 算是休息了这么长时间吧!准备学习下python文本处理了,哪位大大有好书推荐的说下!

    算是休息了这么长时间吧!准备学习下python文本处理了,哪位大大有好书推荐的说下!

  9. GCD: 基本概念和Dispatch Queue 【转】

    什么是GCD? Grand Central Dispatch或者GCD,是一套低层API,提供了一种新的方法来进行并发程序编写.从基本功能上讲,GCD有点像 NSOperationQueue,他们都允 ...

随机推荐

  1. 【转】mysql存储引擎

    http://www.cnblogs.com/kevingrace/p/5685355.html Mysql有两种存储引擎:InnoDB与Myisam,下表是两种引擎的简单对比   MyISAM In ...

  2. redis对比其余数据库

    Redis属于常见的NoSQL数据库或者说非关系数据库:Redis不使用表,她的数据库也不会预定义或者强制去要求用户对Redis存储的不同数据进行关联. 常见数据库对比: 和高性能键值缓存服务器mem ...

  3. JAVA面向对象初步知识总结:封装、继承、多态

    1.封装 把数据和方法包装进类中,以及具体实现的隐藏,常共同被称作是是封装.其结果是一个同时带有特征和行为的数据类型.所谓具体实现的隐藏是通过访问权限控制实现的.JAVA 子类重写继承的方法时,不可以 ...

  4. Phabricator部署手册

    参考:https://secure.phabricator.com/book/phabricator/article/installation_guide/ 概述 phabricator,由faceb ...

  5. AFN 无网络监控

    直接把封装到请求里就好 -(void)afn{ //1.创建网络状态监测管理者 AFNetworkReachabilityManager *manger = [AFNetworkReachabilit ...

  6. Modern C++ CHAPTER 2(读书笔记)

    CHAPTER 2 Recipe 2-1. Initializing Variables Recipe 2-2. Initializing Objects with Initializer Lists ...

  7. String,StringBuffer

    String类代表不可变的字符序列. String s1 = "hello"; String s2 = "hello"; s1 == s2  ==> tr ...

  8. 121. Best Time to Buy and Sell Stock (一) leetcode解题笔记

    121. Best Time to Buy and Sell Stock Say you have an array for which the ith element is the price of ...

  9. link和import的区别

    1.link是xhtml标签,除了可以加载css外,还可以加载定义其他的事务:@import只属于css的范畴,只能加载css: 2.link引用CSS时,在页面载入时同时加载:@import需要页面 ...

  10. SAP第一轮面试总结

    1. 开始是一套面试题,可以选JAVA或C/C++两个语言的英文题.基础语法题,以指针为主. 2. 英文介绍,自由发挥.问了以下几个问题: 离职的愿意,未来五年的计划,介不介意使用ABAP langu ...