前言
这篇文章主要分析dispatch_semaphore_t
的底层实现
GCD信号量
dispatch_semaphore_t
dispatch_semaphore_s
是性能稍次于自旋锁的的信号量对象,用来保证资源使用的安全性。
1 | DISPATCH_DECL(dispatch_semaphore); |
- 1、
semaphore_t
是mach_port_t
信号
1 | typedef mach_port_t semaphore_t; |
- 2、
dispatch_sema_notify_s
的定义如下:
1 | struct dispatch_sema_notify_s { |
现在看起来还是一团散沙,硬着头皮往下看吧
dispatch_semaphore_create
创建一个具有初始值的计数信号量。如果有两个线程共同完成某一项工作时,可以使用一个初始值为0的semaphore。如果你需要管理一个有限的资源池时,应该使用一个初始值为资源池的大小的semaphore。如果不再使用这个semaphore时,应该使用dispatch_release
销毁semaphore对象并且释放他的内存。(在ARC模式下不需要,系统会自动释放)
1 | dispatch_semaphore_t dispatch_semaphore_create(long value){ |
- 首先value必须大于等于0,流程才能继续进行;
- 申请一块
dispatch_semaphore_s
内存,并初始化为0; - 将dsema的
do_vtable
指向_dispatch_semaphore_vtable
,_dispatch_semaphore_vtable
的定义如下:
1 | const struct dispatch_semaphore_vtable_s _dispatch_semaphore_vtable = { |
_dispatch_semaphore_dispose
,顾名思义,销毁一个信号量,定义如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18void _dispatch_semaphore_dispose(dispatch_semaphore_t dsema){
kern_return_t kr;
if (dsema->dsema_value < dsema->dsema_orig) {
DISPATCH_CLIENT_CRASH("Semaphore/group object deallocated while in use");
}
if (dsema->dsema_port) {
kr = semaphore_destroy(mach_task_self(), dsema->dsema_port);
DISPATCH_SEMAPHORE_VERIFY_KR(kr);
}
if (dsema->dsema_waiter_port) {
kr = semaphore_destroy(mach_task_self(), dsema->dsema_waiter_port);
DISPATCH_SEMAPHORE_VERIFY_KR(kr);
}
_dispatch_dispose(dsema);
}
dispatch_semaphore_wait
等待一个semaphore,或者是减少semaphore的计数。每次会执行会将计数器-1.如果减完之后计数器小于0的话,会阻塞在当前线程直到接收到信号。1
2
3
4
5
6
7
8long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout){
// 如果信号量的值-1之后大于等于0,表示有资源可用
if (dispatch_atomic_dec(&dsema->dsema_value) >= 0) {
return 0;
}
return _dispatch_semaphore_wait_slow(dsema, timeout);
}
该函数先把信号量的信号值原子减1,如果大于等于0,则表示有资源可用,直接返回;否则调用_dispatch_semaphore_wait_slow
,等到FIFO队列中信号量的到来,直到timeout为止。
1 | static long _dispatch_semaphore_wait_slow(dispatch_semaphore_t dsema, dispatch_time_t timeout){ |
进入_dispatch_semaphore_wait_slow
之后,根据timeout的类型分三种情况:
- 1、DISPATCH_TIME_NOW:检测后立即返回,不等待,若
dsema_value
小于0,那么将其增加1, 然后返回超时信号KERN_OPERATION_TIMED_OUT
;2、DISPATCH_TIME_FOREVER:无线等待,直到
dsema_value
大于0,若有了信号,会跳转到again,重新执行本函数的流程;
1
2 /* 没有超时的代码 */
kr = semaphore_wait(dsema->dsema_port);3、计时等待,直到有信号到来或者超时。
1
2 /* 有超时的代码 */
kr = slowpath(semaphore_timedwait(dsema->dsema_port, _timeout));
dispatch_semaphore_signal
发送信号,或者增加一个信号量的计数。如果增加之前的信号值dsema_value大于等于0,说明没有线程因为该信号而阻塞,所以直接返回。1
2
3
4
5
6DISPATCH_NOINLINE long dispatch_semaphore_signal(dispatch_semaphore_t dsema){
if (dispatch_atomic_inc(&dsema->dsema_value) > 0) {
return 0;
}
return _dispatch_semaphore_signal_slow(dsema);
}
如果增加之前的信号值小于0,则调用_dispatch_semaphore_signal_slow
,将会唤起一个在spatch_semaphore_wait
中等待的线程。(如果有多个线程在等待,iOS会根据线程的优先级来判断具体唤醒哪个线程)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22DISPATCH_NOINLINE static long _dispatch_semaphore_signal_slow(dispatch_semaphore_t dsema){
kern_return_t kr;
_dispatch_semaphore_create_port(&dsema->dsema_port);
// Before dsema_sent_ksignals is incremented we can rely on the reference
// held by the waiter. However, once this value is incremented the waiter
// may return between the atomic increment and the semaphore_signal(),
// therefore an explicit reference must be held in order to safely access
// dsema after the atomic increment.
_dispatch_retain(dsema);
dispatch_atomic_inc(&dsema->dsema_sent_ksignals);
// 利用系统的信号量库实现发送信号量的功能
kr = semaphore_signal(dsema->dsema_port);
DISPATCH_SEMAPHORE_VERIFY_KR(kr);
_dispatch_release(dsema);
return 1;
}
总结
GCD信号量比较简单:记住这3个关键API:create、wait和signal即可