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. Android停止运行问题1_layout布局xml问题

    好好的写了300多行布局文件代码,写完之后运行结果,停止运行了.我当时就奇怪,xml有错误应该会提示啊,我就一个一个的缩小错误的代码范围,先直接换成一个简单的TextView ,运行一下没有错误.再慢 ...

  2. SQL中EXISTS怎么用[转]

    SQL中EXISTS怎么用 1 2 3 4 分步阅读 EXISTS用于检查子查询是否至少会返回一行数据,该子查询实际上并不返回任何数据,而是返回值True或False 方法/步骤 1 EXISTS用于 ...

  3. POJ3020 匹配

    题目大意:给定一地图,*可以和相邻的*匹配成一对儿,问最少需要对儿匹配才能使所有*都被匹配到. 很直白的最小点覆盖,即ans = 点集数-最大匹配数. 不过一开始要对图进行遍历得到点集,找到一个*就把 ...

  4. NMAP 基本用法

    Nmap 用途: 1.通过对设备或者防火墙的探测来审计它的安全性. 2.探测目标主机所开放的端口. 3.网络存储,网络映射,维护和资产管理.(这个有待深入) 4.通过识别新的服务器审计网络的安全性. ...

  5. python logging模块可能会令人困惑的地方

    python logging模块主要是python提供的通用日志系统,使用的方法其实挺简单的,这块就不多介绍.下面主要会讲到在使用python logging模块的时候,涉及到多个python文件的调 ...

  6. U盘启动盘 安装双系统 详细教程

    U盘启动盘 安装win7+linux双系统 最近在看鸟哥的linux 私房菜 ,看到多重系统那部分,自然的安装多重系统的激情由此而燃.在网上看了很多资料,感觉都不全.经过艰辛的摸索,终于被我发现了一个 ...

  7. js为空的几种情况

    1.null,对象不存在 var ii= document.getElementById("id"); alert(ii); 当前页面不存在id对象 2. undefined  v ...

  8. Ceph剖析:故障检测

    作者:吴香伟 发表于 2014/10/10 版权声明:可以任意转载,转载时务必以超链接形式标明文章原始出处和作者信息以及版权声明 心跳是用于OSD节点间检测对方是否故障的,以便及时发现故障节点进入相应 ...

  9. Jquery基础知识

    //使用$操作得到的对象,都是Jquery对象 如何把Jquery对象转换成dom对象?$abc 方法1:var div = $div.get(0) 方法2:var div = $div[0] 如何把 ...

  10. ServiceStack.OrmLite中的一些"陷阱"(3)

    前文说到如果使用多数据库(不同SQL方言)时要如何开发?其实前文(第二篇)也有“透露”到.就是直接使用库提供的OrmLiteConnection 及OrmLiteConnectionFactory(I ...