线程同步之使用信号量semaphore
基本概念
信号量:信号量就是一个整数,并且具有一个初始计数值。支持两个操作:1.信号通知,2.等待。当一个信号量被通知时,信号量就会加1,当一个信号量收到等待通知时,信号量就减1.
操作GCD信号量的函数
在iOS中有三个函数可以操作GCD信号量:
dispatch_semaphore_t dispatch_semaphore_create(long value);
创建一个信号量。
long dispatch_semaphore_signal(dispatch_semaphore_t dsema);
发送一个信号.
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
等待一个信号。
函数说明:
dispatch_semaphore_t semaphoreTask1 = dispatch_semaphore_create(0);
创建一个信号量,并赋予初始值(最好传一个>=0的值)。eg:传入2则表示同时最多两个线程可以访问临界区。long signalRs = dispatch_semaphore_signal(semaphoreTask1);
发出一个信号,将信号量加1。如果有通过dispatch_semaphore_wait()函数等待dispatch semaphore的计数值增加的线程,那么该函数在返回前将唤醒最先处于等待状态的线程。
返回值:This function returns non-zero if a thread is woken. Otherwise, zero is returned。如果有线程被唤醒则返回一个非0值,否则返回0。long waitRs = dispatch_semaphore_wait(semaphoreTask1, DISPATCH_TIME_FOREVER);
将信号量-1。减去1后如果得到的值<0,则该函数在返回前将一直等待信号的发出,线程会被阻塞。减去1后如果得到的值>0,则正常返回0,不阻塞线程。
timeout:指定等待的时间。如果线程要被阻塞该值表明将阻塞该线程多久。传DISPATCH_TIME_FOREVER意味着该线程永久等待一个信号的发出。 传一个其他值如:dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)),表明将阻塞该线程2s,2s后函数将返回,线程继续执行(不一定就是执行临界区的代码,可以判断如果返回的是非0表明是等待超时了可以做其他处理)。
返回值:Returns zero on success, or non-zero if the timeout occurred。
注意:dispatch_semaphore_signal() 与 dispatch_semaphore_wait() 必须配对使用,否则将导致EXC_BAD_INSTRUCTION
崩溃。
使用场景
假设有A,B两个异步的大任务(需要较多时间才能完成),需要等到这两个异步任务都完成之后,再用它们的结果进行下一步处理.
OK,场景很简单,没毛病.此时,我们自然想到要使用GCD里面的组来解决,在dispatch_group_notify()
里面获取到A,B任务的结果,进行下一步的操作.
A,B两个任务分别放在dispatch_group_async()
完成.
代码如下:
- (void)semaphoreSomething
{
dispatch_group_t group = dispatch_group_create();
__block float a = 0;
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//实际要处理的任务,异步执行的
[self task1WithCompletion:^(float rs) {
a = rs;
}];
});
__block float b = 0;
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self task2WithCompletion:^(float rs) {
b = rs;
}];
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
float c = a - b;
NSLog(@"c = a - b = %f - %f = %f", a, b, c);
});
}
- (void)task1WithCompletion:(void (^)(float rs))completion
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
float rs = [self bigTaskWithCnt:60000000 identify:@"task1"];
if (completion) {
completion(rs);
}
});
}
- (void)task2WithCompletion:(void (^)(float rs))completion
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
float rs = [self bigTaskWithCnt:30000000 identify:@"task2"];
if (completion) {
completion(rs);
}
});
}
- (float)bigTaskWithCnt:(NSInteger)cnt identify:(NSString *)identify
{
NSLog(@"大任务%@开始,线程:%@",identify, [NSThread currentThread]);
float f = 0.0;
for (NSInteger i = 0; i < cnt; i++) {
f = f + sin(sin(sin(time(NULL) + i)));
}
NSLog(@"大任务%@完成:f = %f\n",identify, f);
return f;
}
当你颤抖着运行程序时,却发现,结果并不是你期望的.
2017-02-25 17:19:01.900 信号量[14903:429880] 将要task2,线程:<NSThread: 0x608000268140>{number = 3, name = (null)}
2017-02-25 17:19:01.900 信号量[14903:429883] 将要task1,线程:<NSThread: 0x600000264500>{number = 4, name = (null)}
2017-02-25 17:19:01.900 信号量[14903:429880] 大任务task2开始,线程:<NSThread: 0x608000268140>{number = 3, name = (null)}
2017-02-25 17:19:01.901 信号量[14903:429883] 大任务task1开始,线程:<NSThread: 0x600000264500>{number = 4, name = (null)}
2017-02-25 17:19:01.901 信号量[14903:429599] 任务完成处理结果线程:<NSThread: 0x608000077b00>{number = 1, name = main}
2017-02-25 17:19:01.901 信号量[14903:429599] c = a - b = 0.000000 - 0.000000 = 0.000000
2017-02-25 17:19:11.016 信号量[14903:429880] 大任务task2完成:f = 1.699517
2017-02-25 17:19:20.315 信号量[14903:429883] 大任务task1完成:f = -3.321420
这是因为任务都是异步的,导致操作很快就结束了,这样程序就会进入dispatch_group_notify()
里,执行最后的处理.而由于任务并没有真正完成,所以结果当然不是你所期望的.于是线程同步的问题就出现了!如何让A,B任务真正完成之后才进入dispatch_group_notify()
里?
我们知道semaphore可以通过发出信号和等待信号来让线程唤醒或阻塞.这正是我们所需要的,通过信号量我们可以将操作所在的线程先阻塞住,等到任务真正完成的时候再唤醒线程.
修改上面的代码为:
- (void)semaphoreSomething
{
dispatch_group_t group = dispatch_group_create();
__block float a = 0;
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"将要task1,线程:%@", [NSThread currentThread]);
//创建一个信号量
dispatch_semaphore_t semaphoreTask1 = dispatch_semaphore_create(0);
//实际要处理的任务
[self task1WithCompletion:^(float rs) {
a = rs; //任务结束.
long signalRs = dispatch_semaphore_signal(semaphoreTask1);
NSLog(@"发送信号函数返回结果--非0则表明线程已被唤醒:%ld", signalRs);
}];
//信号量初始化的时候是0,这里-1后=-1<0,于是阻塞住当前的子线程.
long waitRs = dispatch_semaphore_wait(semaphoreTask1, DISPATCH_TIME_FOREVER);
NSLog(@"等待信号函数返回结果--0表明成功,非0表明超时:%ld", waitRs);
});
__block float b = 0;
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"将要task2,线程:%@", [NSThread currentThread]);
dispatch_semaphore_t semaphoreTask2 = dispatch_semaphore_create(0);
[self task2WithCompletion:^(float rs) {
b = rs;
dispatch_semaphore_signal(semaphoreTask2);
}];
dispatch_semaphore_wait(semaphoreTask2, DISPATCH_TIME_FOREVER);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"任务完成处理结果线程:%@", [NSThread currentThread]);
float c = a - b;
NSLog(@"c = a - b = %f - %f = %f", a, b, c);
});
}
再次执行,结果就正常了:
2017-02-25 17:17:02.919 信号量[14870:428590] 将要task1,线程:<NSThread: 0x608000261340>{number = 4, name = (null)}
2017-02-25 17:17:02.919 信号量[14870:428589] 将要task2,线程:<NSThread: 0x600000264e40>{number = 3, name = (null)}
2017-02-25 17:17:02.919 信号量[14870:428592] 大任务task1开始,线程:<NSThread: 0x60000007dd80>{number = 5, name = (null)}
2017-02-25 17:17:02.919 信号量[14870:428618] 大任务task2开始,线程:<NSThread: 0x60000007c200>{number = 6, name = (null)}
2017-02-25 17:17:11.947 信号量[14870:428618] 大任务task2完成:f = 2.249237
2017-02-25 17:17:21.144 信号量[14870:428592] 大任务task1完成:f = 2.456331
2017-02-25 17:17:21.145 信号量[14870:428592] 发送信号函数返回结果--非0则表明线程已被唤醒:1
2017-02-25 17:17:21.145 信号量[14870:428590] 等待信号函数返回结果--0表明成功,非0表明超时:0
2017-02-25 17:17:21.145 信号量[14870:428499] 任务完成处理结果线程:<NSThread: 0x600000070d00>{number = 1, name = main}
2017-02-25 17:17:21.145 信号量[14870:428499] c = a - b = 2.456331 - 2.249237 = 0.207094
当然信号量的使用并不局限于此,信号量可以进行一些细粒度很高的排他操作.
看到这里,我估计肯定有一些小伙伴会对里面的操作,任务等词语感到困惑.没办法很多理解障碍都是因为翻译导致的,本来两个不同的英文单词结果翻译过来都是同一个中文单词.比如"property"和"attribute",翻译过来都是"属性".如果你看的技术文章是中文翻译的,你绝对会一脸懵逼.