开发当中我们经常会碰到这种情况:
- 假设现在系统有两个空闲资源可以被利用,但同一时间却有三个线程要进行访问,这种情况下,该如何处理呢?
- 我们要下载很多图片,并发异步进行,每个下载都会开辟一个新线程,可是我们又担心太多线程肯定cpu吃不消,
那么我们这里也可以用信号量控制一下最大开辟线程数。
1.定义:就是一种可用来控制访问资源的线程数量的标识,设定了一个信号量,在线程访问之前,加上信号量的处理,则可告知系统按照我们指定的信号量数量来执行多个线程。
2.信号量主要有3个函数,分别是:
//根据一个初始值创建信号量
dispatch_semaphore_create(信号量值)
//如果信号量的值<=0,当前线程就会进入休眠等待(直到信号量的值>0);如果信号量的值>0,就减1,然后往下执行后面的代码。
dispatch_semaphore_wait(信号量,等待时间)
//提高信号量(让信号量的值加1)
dispatch_semaphore_signal(信号量)
注意,这两个函数通常成对使用。
dispatch_semaphore实现的原理和自旋锁有点不一样。如果信号量小于等于0,它会使线程进入睡眠状态,让出cpu时间。直到信号量大于0或者超时,则线程会被重新唤醒执行后续操作。
3.我们举例解决一下刚开始提出的问题。
- (void)dispatchSignal {
//创建信号量,参数:信号量的初值,如果小于0则会返回NULL
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
//等待降低信号量
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 1");
sleep(1);
NSLog(@"complete task 1");
//提高信号量
dispatch_semaphore_signal(semaphore);
});
dispatch_async(queue, ^{
//等待降低信号量
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 2");
sleep(1);
NSLog(@"complete task 2");
//提高信号量
dispatch_semaphore_signal(semaphore);
});
dispatch_async(queue, ^{
//等待降低信号量
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 3");
sleep(1);
NSLog(@"complete task 3");
//提高信号量
dispatch_semaphore_signal(semaphore);
});
}
输出结果为:
2018-08-19 22:47:31.118267+0800 TestDate[19223:293646] run task 2
2018-08-19 22:47:31.118267+0800 TestDate[19223:293642] run task 1
2018-08-19 22:47:32.118646+0800 TestDate[19223:293642] complete task 1
2018-08-19 22:47:32.118646+0800 TestDate[19223:293646] complete task 2
2018-08-19 22:47:32.118824+0800 TestDate[19223:293644] run task 3
2018-08-19 22:47:33.121652+0800 TestDate[19223:293644] complete task 3
由于信号量的初始值为2,代表最多开两个线程,所以等待任务1和任务2执行之后才会执行任务3。
当我们将信号量的初始值为1,则是按顺序执行了。
2018-08-19 22:44:42.208884+0800 TestDate[19133:290481] run task 1
2018-08-19 22:44:43.212488+0800 TestDate[19133:290481] complete task 1
2018-08-19 22:44:43.212740+0800 TestDate[19133:290484] run task 2
2018-08-19 22:44:44.213660+0800 TestDate[19133:290484] complete task 2
2018-08-19 22:44:44.213835+0800 TestDate[19133:290482] run task 3
2018-08-19 22:44:45.214455+0800 TestDate[19133:290482] complete task 3
当我们将信号量的初始值为3,则是完全异步执行了。
2018-08-19 22:47:04.676448+0800 TestDate[19198:292866] run task 2
2018-08-19 22:47:04.676448+0800 TestDate[19198:292869] run task 1
2018-08-19 22:47:04.676464+0800 TestDate[19198:292868] run task 3
2018-08-19 22:47:05.679441+0800 TestDate[19198:292869] complete task 1
2018-08-19 22:47:05.679441+0800 TestDate[19198:292866] complete task 2
2018-08-19 22:47:05.679464+0800 TestDate[19198:292868] complete task 3
使用信号量完成同步操作
场景一:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
self.sem = sem;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
[self semaphore_signal];
long semaphoreWait = dispatch_semaphore_wait(sem, dispatch_time(DISPATCH_TIME_NOW, 15 * NSEC_PER_SEC));
NSLog(@"semaphoreWait: %ld", semaphoreWait);
if (semaphoreWait == 0) {
// 降低信号量成功
NSLog(@"降低信号量成功");
} else {
// 降低信号量失败,线程休眠直到15s后会走到这里
NSLog(@"降低信号量失败,线程休眠直到15s后会走到这里");
}
});
}
- (void)semaphore_signal {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
dispatch_semaphore_signal(self.sem);
});
}
输出结果为:
2020-03-03 17:30:20.447806+0800 TestSemaphore[73321:2061719] semaphoreWait: 0
2020-03-03 17:30:20.447978+0800 TestSemaphore[73321:2061719] 降低信号量成功
因为信号量为0的时候,线程阻塞等待,直到2s后,执行了dispatch_semaphore_signal,使得信号量加1,此时信号量大于0,dispatch_semaphore_wait就可以降低信号量成功。
场景二:
如果把[self semaphore_signal]
这行代码注释,输出结果为:
2020-03-03 17:42:05.420673+0800 TestSemaphore[73349:2076403] semaphoreWait: 49
2020-03-03 17:42:05.421105+0800 TestSemaphore[73349:2076403] 降低信号量失败,线程休眠直到15s后会走到这里
这是因为信号量为0,线程阻塞等待,直到15s后,继续执行函数,但是semaphoreWait是大于0的,代表降低信号量失败
场景三:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
self.sem = sem;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
// [self semaphore_signal];
long semaphoreWait = dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
NSLog(@"semaphoreWait: %ld", semaphoreWait);
if (semaphoreWait == 0) {
// 降低信号量成功
NSLog(@"降低信号量成功");
} else {
// 降低信号量失败,线程休眠直到15s后会走到这里
NSLog(@"降低信号量失败,线程休眠直到15s后会走到这里");
}
});
}
dispatch_semaphore_wait
第二个参数改为DISPATCH_TIME_FOREVER
,代表线程一直处于等待状态,不会输出任何东西
dispatch_semaphore 闪退问题
dispatch_semaphore_t semp = dispatch_semaphore_create(1);
dispatch_block_t block = ^{
dispatch_semaphore_signal(semp);
NSLog(@"signal");
};
NSMutableArray *array = [NSMutableArray array];
for (NSInteger i = 0; i < 4; i++) {
NSLog(@"wait");
dispatch_semaphore_wait(semp, DISPATCH_TIME_FOREVER);
if (i > 2) {//当I大于2时,只执行 wait ,没执行signal
break;
}else{ //当I小于等于2时,signal与wait是配对的
block();
}
}
控制台输出如下:
019-07-29 11:12:02.300190+0800 TEST[10987:950615] +[CATransaction synchronize] called within transaction
2019-07-29 11:12:02.439848+0800 TEST[10987:950615] wait
2019-07-29 11:12:02.439934+0800 TEST[10987:950615] signal
2019-07-29 11:12:02.439962+0800 TEST[10987:950615] wait
2019-07-29 11:12:02.439986+0800 TEST[10987:950615] signal
2019-07-29 11:12:02.440009+0800 TEST[10987:950615] wait
2019-07-29 11:12:02.440032+0800 TEST[10987:950615] signal
2019-07-29 11:12:02.440054+0800 TEST[10987:950615] wait
上面代码执行后,wait数大于signal次数,即信号量的当前值小于初始化,超过函数作用域后,会释放信号量,此时会崩溃产生
原因是信号量的销毁会调用_dispatch_semaphore_dispose函数,而此函数会执行信号当前值与初始化值的比较,如果小于初始化值,则直接抛出崩溃。
我们看下此函数的源码:
static void
_dispatch_semaphore_dispose(dispatch_semaphore_t dsema)
{
//信号量的当前值小于初始化,会发生闪退。因为信号量已经被释放了
if (dsema->dsema_value < dsema->dsema_orig) {
DISPATCH_CLIENT_CRASH(
"Semaphore/group object deallocated while in use");
}
#if USE_MACH_SEM
kern_return_t kr;
//释放信号,这个信号是dispatch_semaphore使用的信号
if (dsema->dsema_port) {
kr = semaphore_destroy(mach_task_self(), dsema->dsema_port);
DISPATCH_SEMAPHORE_VERIFY_KR(kr);
}
//释放信号,这个信号是dispatch_group使用的信号
if (dsema->dsema_waiter_port) {
kr = semaphore_destroy(mach_task_self(), dsema->dsema_waiter_port);
DISPATCH_SEMAPHORE_VERIFY_KR(kr);
}
#elif USE_POSIX_SEM
int ret = sem_destroy(&dsema->dsema_sem);
DISPATCH_SEMAPHORE_VERIFY_RET(ret);
#endif
_dispatch_dispose(dsema);
}
因此。当信号量的当前值小于初始化,释放信号量时,会导致崩溃,简而言之就是,signal的调用次数一定要大于等于wait的调用次数,否则导致崩溃。
网友评论