上一篇 [iOS多线程-GCD之dispatch_barrier_async]
(http://www.jianshu.com/p/d63c3100dd63)
一、前言
Semaphore,一般称作信号量,相信学习过GCD的童靴对它一定不陌生,它的类型全称是 dispatch_queue_t。本篇我们先介绍一下semaphore.h里面的接口,然后再引用AFNetworking中的代码片段分析它的应用场景。
二、semaphore.h很简单
先看一个非常简单的加锁代码,semaphore一般的用法都和这个例子类似:
- (void)testDispatchSemaphore
{
//获取一个全局队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//可变数组
NSMutableArray *mutableArray = [NSMutableArray array];
//创建一个计数为1的信号量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
for (int i = 0; i < 100000; ++i) {
dispatch_async(queue, ^{
//等待semaphore计数大于等于1,减1而不等待
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[mutableArray addObject:[NSNumber numberWithInt:i]];
//执行完后将semaphore计数加1
dispatch_semaphore_signal(semaphore);
});
}
}
你可以尝试把semaphore相关的代码注释掉,运行一遍(bu beng kui lai zhao wo)。好了,代码我们稍后再分析。回到semephore.h,如何找到这个头文件呢?随便打开一个工程,在某个方法里面写上dispatch_queue_ t
,command+左键进入到文件里面,它被包括在GCD的库文件dispatch里面,其中只有三个方法:
//创建一个计数为value的信号量,value为同时可访问的线程数
dispatch_semaphore_t dispatch_semaphore_create(long value);
//如果信号量大于零,则不会阻塞当前线程,并且信号量减1;如果等于零,阻塞当前线程直到信号量大于零
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
//将信号量计数加1,这样会唤醒一个被阻塞的线程
long dispatch_semaphore_signal(dispatch_semaphore_t dsema);
好,我们再来看示例代码。代码尝试在多线程的环境下给一个可变数组添加对象,由于NSMutableArray并不是线程安全的,因此我们得为添加对象的代码加锁。没错,这就是semaphore的第一个用法了
信号量并不是一个整形或浮点型,它本质是一个dispatch_object类型,只能通过signal和wait函数来加1和减1。我推测(xia bai),它是一个Objective-C类型的结构体,其中包含一个计数的变量,所以更专业的说法是“信号量计数”加1或减1,这里我就直接说成信号量加1或减1了。
一、问题
运行下面这段代码:
- (void)testDispatchSemaphore
{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSMutableArray *mutableArray = [NSMutableArray array];
for (int i = 0; i < 100000; ++i) {
dispatch_async(queue, ^{
[mutableArray addObject:[NSNumber numberWithInt:i]];
});
}
}
结果异常
GCDLearn(907,0x7000002a0000) malloc: *** error for object 0x7faf63823c00: double free
*** set a breakpoint in malloc_error_break to debug
毫无疑问,会产生异常,原因是:在多个线程中对mutableArray添加对象时,访问了同一块内存,产生了资源竞争。这时候大家都会想到加锁了,Dispatch Semaphore就可以完成这一功能。
线程锁的实现方式有多种,自旋锁OSSpinLock已经不再安全,Dispatch Semaphore是目前性能最好的加锁方式,参考:http://www.jianshu.com/p/8b8a01dd6356
二、解决
Dispatch Semaphore加锁由以下三行代码实现
//创建信号量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
//信号量计数减1
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
//信号量计数加1
dispatch_semaphore_signal(semaphore);
dispatch_semaphore_create(1)
创建一个计数为1的信号量semaphore;
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
当semaphore为0时,会一直等待semaphore大于或等于1;大于等于1时,将semaphore计数减1而不用等待;
dispatch_semaphore_signal(semaphore)
将semaphore计数加1。
用Dispatch Semaphore解决上面的问题:
- (void)testDispatchSemaphore
{
//获取一个全局队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//可变数组
NSMutableArray *mutableArray = [NSMutableArray array];
//创建一个计数为1的信号量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
for (int i = 0; i < 100000; ++i) {
dispatch_async(queue, ^{
//等待semaphore计数大于等于1,减1而不等待
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[mutableArray addObject:[NSNumber numberWithInt:i]];
//执行完后将semaphore计数加1
dispatch_semaphore_signal(semaphore);
});
}
}
执行代码,不会再崩溃了,ok!
分析:semaphore初始化计数为1能够保证同一时间只有一个线程能够执行这段代码,达到了加锁的目的。