版本记录
版本号 | 时间 |
---|---|
V1.0 | 2017.08.15 |
前言
信号量机制是多线程通信中的比较重要的一部分,对于
NSOperation
可以设置并发数,但是对于GCD
就不能设置并发数了,那么就只能靠信号量机制了。接下来这几篇就会详细的说一下并发机制。
基本概念
信号量
信号量其实就是一个整数值并具有一个初始化的值,有关信号量有两个操作:
- 信号通知
- 信号等待
这里我们需要注意:
- 当信号量被信号通知时,其计数会被增加。
- 当线程在信号量等待时,线程会阻塞,计数会减少。
GCD中的信号量
信号量控制互斥的原理
信号量就是一个资源计数器,对信号量有两个操作来达到互斥,分别是P
和V
操作。 一般情况是这样进行临界访问或互斥访问的: 设信号量值为1, 当一个进程1运行是,使用资源,进行P操作,即对信号量值减1,也就是资源数少了1个。这是信号量值为0。系统中规定当信号量值为0是,必须等待,知道信号量值不为零才能继续操作。 这时如果进程2想要运行,那么也必须进行P操作,但是此时信号量为0,所以无法减1,即不能P操作,也就阻塞。这样就到到了进程1排他访问。 当进程1运行结束后,释放资源,进行V操作。资源数重新加1,这是信号量的值变为1, 这时进程2发现资源数不为0,信号量能进行P操作了,立即执行P操作。信号量值又变为0,次数进程2咱有资源,排他访问资源,这就是信号量来控制互斥的原理。
总的来说,信号量为0时就阻塞线程,大于0就不会阻塞,通过改变信号量的值控制线程的阻塞,达到线程的同步。
三种重要的函数
GCD
中的信号量有三种操作函数:
-
dispatch_semaphore_create
: 创建一个信号量,具有整形的数值,即为信号的总量。 -
dispatch_semaphore_signal
:- 返回值为
long
类型,当返回值为0时,表示当前并没有线程等待其处理的信号量,其处理的信号总量增加1。 - 当返回值不为0时,表示其当前有一个或者多个线程等待其处理的信号量,并且该函数唤醒了一个等待的线程(当线程有优先级的时候,唤醒优先级最高的线程,否则随机唤醒)。
- 返回值为
-
dispatch_semaphore_wait
:- 等待信号,具体操作是首先判断信号量
desema
是否大于0,如果大于0就减掉1个信号,往下执行; - 如果等于0函数就阻塞该线程等待
timeout
(注意timeout
类型为dispatch_time_t
)时,其所处线程自动执行其后的语句。
- 等待信号,具体操作是首先判断信号量
下面我们就详细的说一下这几个函数。
1. dispatch_semaphore_create
/*!
* @function dispatch_semaphore_create
*
* @abstract
* // 创建具有初始值的新计数信号量。
* Creates new counting semaphore with an initial value.
*
* @discussion
* Passing zero for the value is useful for when two threads need to reconcile
* the completion of a particular event. Passing a value greater than zero is
* useful for managing a finite pool of resources, where the pool size is equal
* to the value.
* //如果两个线程需要调整特定事件的完成,则给该值传递0。 传递大于零的值对于管理有限的资源池很有用,其中池大小等于该值。
*
* @param value
* The starting value for the semaphore. Passing a value less than zero will
* cause NULL to be returned.
* //信号的初始值,传递小于0的值会返回NULL
*
* @result
* The newly created semaphore, or NULL on failure.
* //返回新创建的信号量,或者失败时返回NULL
*/
__OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_4_0)
DISPATCH_EXPORT DISPATCH_MALLOC DISPATCH_RETURNS_RETAINED DISPATCH_WARN_RESULT
DISPATCH_NOTHROW
dispatch_semaphore_t
dispatch_semaphore_create(long value);
2. dispatch_semaphore_signal
/*!
* @function dispatch_semaphore_signal
*
* @abstract
* Signal (increment) a semaphore.
* //信号量增加
*
* @discussion
* Increment the counting semaphore. If the previous value was less than zero,
* this function wakes a waiting thread before returning.
*//增加计数信号量。 如果以前的值小于零,则此函数在返回之前唤醒等待线程。
*
* @param dsema The counting semaphore.
* The result of passing NULL in this parameter is undefined.
*// 该参数传递NULL时,返回的结果未定义。
*
* @result
* This function returns non-zero if a thread is woken. Otherwise, zero is
* returned.
*//如果线程被唤醒,该函数返回的是个非0数,否则,返回的是0
*/
__OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_4_0)
DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_NOTHROW
long
dispatch_semaphore_signal(dispatch_semaphore_t dsema);
3. dispatch_semaphore_wait
/*!
* @function dispatch_semaphore_wait
*
* @abstract
* Wait (decrement) for a semaphore.
*//等待(递减)一个信号量
*
* @discussion
* Decrement the counting semaphore. If the resulting value is less than zero,
* this function waits for a signal to occur before returning.
*//减少计数信号量。 如果结果值小于零,则此函数在返回之前等待信号发生。
*
* @param dsema
* The semaphore. The result of passing NULL in this parameter is undefined.
*//信号量,给这个参数传递NULL的结果没有定义。
*
* @param timeout
* When to timeout (see dispatch_time). As a convenience, there are the
* DISPATCH_TIME_NOW and DISPATCH_TIME_FOREVER constants.
*//超时(请参阅dispatch_time)的时候, 为方便起见,有DISPATCH_TIME_NOW和DISPATCH_TIME_FOREVER常量。
*
* @result
* Returns zero on success, or non-zero if the timeout occurred.
*//成功后返回0,超时发生的时候返回非0数。
*/
__OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_4_0)
DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_NOTHROW
long
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
信号量的一种形象比喻
下面就以停车做例子说明信号量这几个函数的使用。
-
停车场剩余
4
个车位,那么即使同时来了四辆车也能停的下。如果此时来了五辆车,那么就有一辆需要等待。 -
信号量的值就相当于剩余车位的数目,
dispatch_semaphore_wait
函数就相当于来了一辆车,dispatch_semaphore_signal
就相当于走了一辆车。停车位的剩余数目在初始化的时候就已经指明了dispatch_semaphore_create(long value)
。 -
调用一次
dispatch_semaphore_signal
,剩余的车位就增加一个;调用一次dispatch_semaphore_wait
剩余车位就减少一个; -
当剩余车位为
0
时,再来车(即调用dispatch_semaphore_wait
)就只能等待。有可能同时有几辆车等待一个停车位。有些车主没有耐心,给自己设定了一段等待时间,这段时间内等不到停车位就走了,如果等到了就开进去停车。而有些车主就像把车停在这,所以就一直等下去。
几个简单的例子
例1
下面看一个简单的使用例子。
#import "JJGCDSemaphoreVC.h"
@interface JJGCDSemaphoreVC ()
@end
@implementation JJGCDSemaphoreVC
#pragma mark - OVerride Base Function
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
//创建一个为1信号量的信号
// 打印输出:<OS_dispatch_semaphore: semaphore[0x174099b40] = { xrefcnt = 0x1, refcnt = 0x1, port = 0x0, value = 1, orig = 1 }>
dispatch_semaphore_t signal = dispatch_semaphore_create(1);
__block long x = 0;
NSLog(@"0_x:%ld",x);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
NSLog(@"waiting");
//此时信号量为0 对signal增加1 信号量变为1,
x = dispatch_semaphore_signal(signal);
NSLog(@"1_x:%ld",x);
sleep(2);
NSLog(@"waking");
x = dispatch_semaphore_signal(signal);
NSLog(@"2_x:%ld",x);
});
//此时信号量为1 所以执行下边,对signal减掉1,然后信号量为0
x = dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
NSLog(@"3_x:%ld",x);
//此时信号量为0,永远等待,在等待的时候执行block了,在等待block时候block内对信号量增加了1,然后开始执行下边,并且信号量再次减掉1 变为0
x = dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
NSLog(@"wait 2");
NSLog(@"4_x:%ld",x);
//此时信号量为0,永远等待,在等待的时候执行block了,在等待block时候block内对信号量增加了1,然后开始执行下边,并且信号量再次减掉1 变为0
x = dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
NSLog(@"wait 3");
NSLog(@"5_x:%ld",x);
sleep(2);
x = dispatch_semaphore_signal(signal);
NSLog(@"6_x:%ld",x);
}
@end
下面看输出结果,这里要注意的是调用顺序和信号量的值。
2017-08-16 14:46:15.126895+0800 JJOC[10085:4483826] 0_x:0
2017-08-16 14:46:15.126978+0800 JJOC[10085:4483826] 3_x:0
2017-08-16 14:46:16.128546+0800 JJOC[10085:4483865] waiting
2017-08-16 14:46:16.128746+0800 JJOC[10085:4483826] wait 2
2017-08-16 14:46:16.128803+0800 JJOC[10085:4483826] 4_x:0
2017-08-16 14:46:16.128870+0800 JJOC[10085:4483865] 1_x:1
2017-08-16 14:46:18.132708+0800 JJOC[10085:4483865] waking
2017-08-16 14:46:18.132878+0800 JJOC[10085:4483826] wait 3
2017-08-16 14:46:18.132959+0800 JJOC[10085:4483826] 5_x:0
2017-08-16 14:46:18.133062+0800 JJOC[10085:4483865] 2_x:1
2017-08-16 14:46:20.134107+0800 JJOC[10085:4483826] 6_x:0
例2
#import "JJGCDSemaphoreVC.h"
@interface JJGCDSemaphoreVC ()
@end
@implementation JJGCDSemaphoreVC
#pragma mark - OVerride Base Function
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
[self demo2];
}
#pragma mark - Object Private Function
- (void)demo2
{
// 创建队列组
dispatch_group_t group = dispatch_group_create();
// 创建信号量,并且设置值为10
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < 100; i++)
{
// 由于是异步执行的,所以每次循环Block里面的dispatch_semaphore_signal根本还没有执行就会执行dispatch_semaphore_wait,
//从而semaphore-1.当循环10次后,semaphore等于0,则会阻塞线程,直到执行了Block的dispatch_semaphore_signal 才会继续执行
NSInteger value = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"1_%ld",value);
dispatch_group_async(group, queue, ^{
NSLog(@"%i",i);
sleep(3);
// 每次发送信号则semaphore会+1,
NSInteger value = dispatch_semaphore_signal(semaphore);
NSLog(@"2_%ld",value);
});
}
}
@end
下面看一部分输出结果
2017-08-16 15:15:16.975989+0800 JJOC[10107:4488195] 1_0
2017-08-16 15:15:19.638091+0800 JJOC[10107:4488195] 1_0
2017-08-16 15:16:21.910431+0800 JJOC[10107:4488195] 1_0
2017-08-16 15:16:23.324425+0800 JJOC[10107:4488195] 1_0
2017-08-16 15:16:24.651183+0800 JJOC[10107:4488195] 1_0
2017-08-16 15:16:26.033626+0800 JJOC[10107:4488195] 1_0
2017-08-16 15:16:45.187604+0800 JJOC[10107:4488195] 1_0
2017-08-16 15:16:46.250799+0800 JJOC[10107:4488195] 1_0
2017-08-16 15:16:47.151037+0800 JJOC[10107:4488195] 1_0
2017-08-16 15:16:48.017023+0800 JJOC[10107:4488195] 1_0
2017-08-16 15:16:52.117765+0800 JJOC[10107:4488216] 1
2017-08-16 15:16:52.118002+0800 JJOC[10107:4488217] 0
2017-08-16 15:20:20.225583+0800 JJOC[10107:4488384] 2
2017-08-16 15:20:20.225801+0800 JJOC[10107:4488195] 1_0
2017-08-16 15:20:26.887084+0800 JJOC[10107:4488216] 2_1
2017-08-16 15:22:02.490991+0800 JJOC[10107:4488216] 3
2017-08-16 15:22:02.491183+0800 JJOC[10107:4488195] 1_0
2017-08-16 15:22:05.175372+0800 JJOC[10107:4488217] 2_1
2017-08-16 15:22:06.512384+0800 JJOC[10107:4488217] 4
2017-08-16 15:22:06.512568+0800 JJOC[10107:4488195] 1_0
2017-08-16 15:22:08.819089+0800 JJOC[10107:4488216] 2_1
2017-08-16 15:22:09.809285+0800 JJOC[10107:4488216] 5
2017-08-16 15:22:09.809472+0800 JJOC[10107:4488195] 1_0
(lldb)
大家分析一下输出结果就可以看到线程信息的同步。
参考文献
1. iOS开发系列-信号量
2. 关于dispatch_semaphore的使用
3. 浅谈GCD中的信号量
后记
秋未完,待续~~~
网友评论
2017-08-16 14:46:15.126895+0800 JJOC[10085:4483826] 0_x:0
2017-08-16 14:46:15.126978+0800 JJOC[10085:4483826] 3_x:0
2017-08-16 14:46:16.128546+0800 JJOC[10085:4483865] waiting
2017-08-16 14:46:16.128746+0800 JJOC[10085:4483826] wait 2
但在waiting之后不应该是打印1_x:这个的值么,还是说因为线程的原因,这两个打印顺序都是可以的