1.信号量使用的情况
信号量一般用来进行临界访问或者互斥访问的。临界资源可以理解为共享资源,这个共享资源每次每次只允许一个进程进行访问,当一个线程进入后,其他线程是不允许访问这一块资源的,只能等这一线程访问完后才能进入访问。
2.P V操作
P V操作主要是为了对信号量进行申请或者释放。P操作表示申请一个资源,V操作表示释放一个资源。信号量由一个值和一个指针组成,指针指向等待该信号量的进程。信号量的值表示相应资源的使用情况。当信号量S>=0时,S表示可用资源的数量。执行P操作意味着请求资源,此时S减一,当S<0时,表示没有可用的资源,此时S的绝对值表示当前等待该资源的进程数。这些进程必须等资源被释放后才能继续运行。而执行V操作意味着释放一个资源,S加1。
3.GCD中的信号量
GCD中有三个函数是semaphore的操作:
Dispatch_semaphore_create //创建一个信号量
Dispatch_semaphore_signal //发送一个信号,让信号量增加一
Dispatch_semaphore_wait //等待信号,如果信号总量大于0,则减掉一个信号量
4.GCD中信号量的使用
(1)首先看一段并发的代码
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < 10; i ++) {
NSLog(@"第一组现在是%d",i);
}
});
NSLog(@"现在我执行了");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < 10; i ++) {
NSLog(@"第二组现在是%d",i);
}
});
输出结果:
2017-03-01 15:01:43.714 信号量[6437:260451] 第一组现在是1
2017-03-01 15:01:43.714 信号量[6437:260466] 第二组现在是0
2017-03-01 15:01:43.714 信号量[6437:260466] 第二组现在是1
2017-03-01 15:01:43.714 信号量[6437:260451] 第一组现在是2
2017-03-01 15:01:43.714 信号量[6437:260466] 第二组现在是2
2017-03-01 15:01:43.714 信号量[6437:260451] 第一组现在是3
2017-03-01 15:01:43.714 信号量[6437:260466] 第二组现在是3
2017-03-01 15:01:43.715 信号量[6437:260451] 第一组现在是4
2017-03-01 15:01:43.715 信号量[6437:260466] 第二组现在是4
2017-03-01 15:01:43.715 信号量[6437:260451] 第一组现在是5
2017-03-01 15:01:43.715 信号量[6437:260466] 第二组现在是5
2017-03-01 15:01:43.715 信号量[6437:260451] 第一组现在是6
2017-03-01 15:01:43.715 信号量[6437:260466] 第二组现在是6
2017-03-01 15:01:43.715 信号量[6437:260451] 第一组现在是7
2017-03-01 15:01:43.715 信号量[6437:260466] 第二组现在是7
2017-03-01 15:01:43.715 信号量[6437:260451] 第一组现在是8
2017-03-01 15:01:43.715 信号量[6437:260466] 第二组现在是8
2017-03-01 15:01:43.715 信号量[6437:260451] 第一组现在是9
2017-03-01 15:01:43.716 信号量[6437:260466] 第二组现在是9
从上面的输出结果来看这一段代码是并发执行的,第一组和第二组交替执行。
关于进程、线程的概念以及多线程可以参考我另一篇文章
iOS多线程的简介及使用(http://www.jianshu.com/p/59da6f924e95)
(2)那么现在我想上面的代码第一组执行完了再执行第二段该怎么办呢,下面我们加入信号量来试一下
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(15.0 * NSEC_PER_SEC));
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < 10; i ++) {
NSLog(@"第一组现在是%d",i);
}
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, time);
NSLog(@"现在我执行了");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < 10; i ++) {
NSLog(@"第二组现在是%d",i);
}
});
输出结果:
2017-03-01 15:08:37.848 信号量[6577:266811] 第一组现在是0
2017-03-01 15:08:37.848 信号量[6577:266811] 第一组现在是1
2017-03-01 15:08:37.848 信号量[6577:266811] 第一组现在是2
2017-03-01 15:08:37.848 信号量[6577:266811] 第一组现在是3
2017-03-01 15:08:37.849 信号量[6577:266811] 第一组现在是4
2017-03-01 15:08:37.849 信号量[6577:266811] 第一组现在是5
2017-03-01 15:08:37.849 信号量[6577:266811] 第一组现在是6
2017-03-01 15:08:37.849 信号量[6577:266811] 第一组现在是7
2017-03-01 15:08:37.849 信号量[6577:266811] 第一组现在是8
2017-03-01 15:08:37.850 信号量[6577:266811] 第一组现在是9
2017-03-01 15:08:37.850 信号量[6577:266778] 现在我执行了
2017-03-01 15:08:37.850 信号量[6577:266811] 第二组现在是0
2017-03-01 15:08:37.850 信号量[6577:266811] 第二组现在是1
2017-03-01 15:08:37.850 信号量[6577:266811] 第二组现在是2
2017-03-01 15:08:37.851 信号量[6577:266811] 第二组现在是3
2017-03-01 15:08:37.851 信号量[6577:266811] 第二组现在是4
2017-03-01 15:08:37.851 信号量[6577:266811] 第二组现在是5
2017-03-01 15:08:37.851 信号量[6577:266811] 第二组现在是6
2017-03-01 15:08:37.851 信号量[6577:266811] 第二组现在是7
2017-03-01 15:08:37.852 信号量[6577:266811] 第二组现在是8
2017-03-01 15:08:37.852 信号量[6577:266811] 第二组现在是9
从上面的结果来看我们的代码达到了我们预期的效果,那么我们来分析一下上面的代码我们到底做了什么事情:
首先我们创建了一个信号量,这个信号量的初始值为0
然后我们设置了一个超时时间,在第一组的循环结束时我们释放了信号量,即进行了V操作,然后在第二组循环代码前面我们进行了P操作,请求了信号量我们的代码相比上面的不同点之处是多了这4句代码
代码的不同之处分析完了,那么我们再来分析一下为什么会出现这种结果:
信号量的初始值为0,然后我们在一个并行队列里面异步执行一个for 循环,因为是异步,所以我们可以直接往下面的代码走,这是我们进行了P操作,信号量要减一,因为信号量此时已经为0,我们可以理解为没有多余资源,此时要堵塞线程等待资源,然后等第一组的for循环执行完毕之后,信号量被加1,此时有了资源,于是代码继续往下执行,进入第二组for循环。
(3)看到这里你是不是已经认为自己已经掌握了信号量,觉得自己能独步武林,笑傲江湖,年轻人 too young too simple我们再来看一段代码:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(15.0 * NSEC_PER_SEC));
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < 10; i ++) {
NSLog(@"第一组现在是%d",i);
dispatch_semaphore_signal(semaphore);
}
});
dispatch_semaphore_wait(semaphore, time);
NSLog(@"现在我执行了");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < 10; i ++) {
NSLog(@"第二组现在是%d",i);
}
});
输出结果:
2017-03-01 15:34:02.068 信号量[7015:284862] 第一组现在是0
2017-03-01 15:34:02.069 信号量[7015:284862] 第一组现在是1
2017-03-01 15:34:02.069 信号量[7015:284816] 现在我执行了
2017-03-01 15:34:02.069 信号量[7015:284862] 第一组现在是2
2017-03-01 15:34:02.069 信号量[7015:284863] 第二组现在是0
2017-03-01 15:34:02.069 信号量[7015:284862] 第一组现在是3
2017-03-01 15:34:02.069 信号量[7015:284863] 第二组现在是1
2017-03-01 15:34:02.069 信号量[7015:284862] 第一组现在是4
2017-03-01 15:34:02.069 信号量[7015:284863] 第二组现在是2
2017-03-01 15:34:02.069 信号量[7015:284862] 第一组现在是5
2017-03-01 15:34:02.069 信号量[7015:284863] 第二组现在是3
2017-03-01 15:34:02.070 信号量[7015:284862] 第一组现在是6
2017-03-01 15:34:02.070 信号量[7015:284863] 第二组现在是4
2017-03-01 15:34:02.070 信号量[7015:284862] 第一组现在是7
2017-03-01 15:34:02.070 信号量[7015:284863] 第二组现在是5
2017-03-01 15:34:02.070 信号量[7015:284862] 第一组现在是8
2017-03-01 15:34:02.070 信号量[7015:284863] 第二组现在是6
2017-03-01 15:34:02.070 信号量[7015:284862] 第一组现在是9
2017-03-01 15:34:02.070 信号量[7015:284863] 第二组现在是7
2017-03-01 15:34:02.071 信号量[7015:284863] 第二组现在是8
2017-03-01 15:34:02.071 信号量[7015:284863] 第二组现在是9
看到这个输出结果,有些人可能一脸懵逼,为什么,我觉得这代码一样啊,我们再来仔细看看这段代码与上面的有什么不同,仔细看我们会发现唯一的不同点在于信号量的发送时间不同,即进行V操作的时间不一样,在上面的代码里面我们是在for循环结束之后才进行发送信号量的操作,但是在这里我们是每进行一次for循环我们就发送一次信号量,这样其实信号量一直在增加,于是第二个for 循环就不会被阻塞,就出现了我们看到的结果(其实这一块第一组的0一定是先于现在我执行了这句话执行的,童鞋们可以多试一试看看是不是这种情况)
(4)我们再来看一种情况
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(15.0 * NSEC_PER_SEC));
dispatch_semaphore_wait(semaphore, time);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < 10; i ++) {
NSLog(@"第一组现在是%d",i);
}
dispatch_semaphore_signal(semaphore);
});
NSLog(@"现在我执行了");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < 10; i ++) {
NSLog(@"第二组现在是%d",i);
}
});
输出结果:
2017-03-01 16:10:43.640 信号量[7549:307322] 第一组现在是0
2017-03-01 16:10:43.640 信号量[7549:307270] 现在我执行了
2017-03-01 16:10:43.640 信号量[7549:307322] 第一组现在是1
2017-03-01 16:10:43.640 信号量[7549:307743] 第二组现在是0
2017-03-01 16:10:43.640 信号量[7549:307322] 第一组现在是2
2017-03-01 16:10:43.641 信号量[7549:307743] 第二组现在是1
2017-03-01 16:10:43.641 信号量[7549:307322] 第一组现在是3
2017-03-01 16:10:43.641 信号量[7549:307743] 第二组现在是2
2017-03-01 16:10:43.641 信号量[7549:307322] 第一组现在是4
2017-03-01 16:10:43.641 信号量[7549:307743] 第二组现在是3
2017-03-01 16:10:43.641 信号量[7549:307322] 第一组现在是5
2017-03-01 16:10:43.642 信号量[7549:307743] 第二组现在是4
2017-03-01 16:10:43.642 信号量[7549:307322] 第一组现在是6
2017-03-01 16:10:43.642 信号量[7549:307743] 第二组现在是5
2017-03-01 16:10:43.642 信号量[7549:307322] 第一组现在是7
2017-03-01 16:10:43.642 信号量[7549:307743] 第二组现在是6
2017-03-01 16:10:43.642 信号量[7549:307322] 第一组现在是8
2017-03-01 16:10:43.643 信号量[7549:307743] 第二组现在是7
2017-03-01 16:10:43.643 信号量[7549:307322] 第一组现在是9
2017-03-01 16:10:43.643 信号量[7549:307743] 第二组现在是8
2017-03-01 16:10:43.643 信号量[7549:307743] 第二组现在是9
从输出结果上来看好像两个for循环是并发执行的,但是童鞋们可以跑一下这段代码,你会发现程序运行起来之后会等一段时间才会有输出结果,这是为什么呢,因为刚开始信号量是0,然后我们设置了一个超时时间,当进行P操作对信号量减一的时候会堵塞,因为没有可用资源,等到超时时间过去程序才会继续往下走。这就导致了我们前期有一段时间一直没有输出结果,其实是因为线程被堵塞了。
总结:对信号量进行P V操作一定要考虑清楚应该在什么时候进行,不然可能会达不到我们预期的效果,同时当信号量为0的时候代码是可以继续往下执行的,只要你不进行P操作(有些博客上说当信号量为0 的时候就阻塞了,但我测试为0的时候代码是可以往下执行的,不知道是不是我理解的有偏差),当被阻塞的时候,过了超时时间代码也可以继续往下执行。以上就是我对信号量的初步理解,有什么不对的还要大家多指教,如果有什么好的关于信号量的博客大家也可以推荐一下,共同研究。
网友评论