本篇文章从通过源码分析来研讨其完结原理。GCD
的源码在libdispatch
库中完结的能够在Apple Open Source下载。
1.创建信号量
通过dispatch_semaphore_create(value)
创建一个信号量:
/*!
* @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.
*
* @param value
* The starting value for the semaphore. Passing a value less than zero will
* cause NULL to be returned.
*
* @result
* The newly created semaphore, or NULL on failure.
*/
API_AVAILABLE(macos(10.6), ios(4.0))
DISPATCH_EXPORT DISPATCH_MALLOC DISPATCH_RETURNS_RETAINED DISPATCH_WARN_RESULT
DISPATCH_NOTHROW
dispatch_semaphore_t dispatch_semaphore_create(long value);
连注释一同贴上来了:
- 依据初始值
value
创建一个计数信号量
- 当两个线程需求协同完结任务时,传
0
比较合适。当用来处理有限的资源池时,传大于0
的值更合适
-
value
:信号量的初始值。假设小于0
,将创建失利,函数回来NULL
dispatch_semaphore_create()
的完结:
dispatch_semaphore_t dispatch_semaphore_create(long value)
{
dispatch_semaphore_t dsema;
// If the internal value is negative, then the absolute of the value is
// equal to the number of waiting threads. Therefore it is bogus to
// initialize the semaphore with a negative value.
if (value < 0) {
return DISPATCH_BAD_INPUT;
}
dsema = _dispatch_object_alloc(DISPATCH_VTABLE(semaphore),
sizeof(struct dispatch_semaphore_s));
dsema->do_next = DISPATCH_OBJECT_LISTLESS;
dsema->do_targetq = _dispatch_get_default_queue(false);
dsema->dsema_value = value;
_dispatch_sema4_init(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);
dsema->dsema_orig = value;
return dsema;
}
代码比较简单,就是入参查看(非负),开辟内存空间,初始化结构体成员。
2.等候信号量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.
*
* @param timeout
* When to timeout (see dispatch_time). As a convenience, there are the
* DISPATCH_TIME_NOW and DISPATCH_TIME_FOREVER constants.
*
* @result
* Returns zero on success, or non-zero if the timeout occurred.
*/
API_AVAILABLE(macos(10.6), ios(4.0))
DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_NOTHROW
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
- 等候一个信号,会对信号量
-1
,假设信号量小于0
,该函数不会回来,直到等到一个信号产生(signal
)
- 第一个参数,不能为
NULL
- 第二个参数
timeout
:指定等候的超时时间
- 回来值:成功回来
0
,假设超时回来非0
函数完结:
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
{
long value = os_atomic_dec2o(dsema, dsema_value, acquire);
if (likely(value >= 0)) {
return 0;
}
return _dispatch_semaphore_wait_slow(dsema, timeout);
}
- 首要测验获取锁,在获取锁的时候会对信号量
-1
,假设剩余信号量>= 0
,函数直接回来成功
- 信号量小于
0
,调用_dispatch_semaphore_wait_slow
,该函数会等到信号后才会回来
_dispatch_semaphore_wait_slow()
的完结:
DISPATCH_NOINLINE
static long _dispatch_semaphore_wait_slow(dispatch_semaphore_t dsema,
dispatch_time_t timeout)
{
long orig;
_dispatch_sema4_create(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);
switch (timeout) {
default:
if (!_dispatch_sema4_timedwait(&dsema->dsema_sema, timeout)) {
break;
}
// Fall through and try to undo what the fast path did to
// dsema->dsema_value
case DISPATCH_TIME_NOW:
orig = dsema->dsema_value;
while (orig < 0) {
if (os_atomic_cmpxchgvw2o(dsema, dsema_value, orig, orig + 1,
&orig, relaxed)) {
return _DSEMA4_TIMEOUT();
}
}
// Another thread called semaphore_signal().
// Fall through and drain the wakeup.
case DISPATCH_TIME_FOREVER:
_dispatch_sema4_wait(&dsema->dsema_sema);
break;
}
return 0;
}
- 函数内部创建一个部分的锁,这个锁会保存在我们通过
dispatch_semaphore_create()
创建的信号量的dsema_sema
成员,即dsema->dsema_sema
-
timeout
:
-
- 大多数情况下我们会使用
DISPATCH_TIME_FOREVER
,这个分支下,会直接wait
,使线程进入休眠,等到信号后会唤醒线程
- 大多数情况下我们会使用
-
- 自界说超时时间会走
default
分支,也会休眠线程,不同的是,它是计时休眠,假设超时线程也会被唤醒
- 自界说超时时间会走
-
-
DISPATCH_TIME_NOW
:超时时间就是现在,会立即超时,不知道什么场景会用。这个分支会这么写是因为上面default
分支,假设是超时唤醒了线程,会直接「贯穿」到该分支,超时是在这个分支回来的
-
3.发送信号量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.
*
* @result
* This function returns non-zero if a thread is woken. Otherwise, zero is
* returned.
*/
API_AVAILABLE(macos(10.6), ios(4.0))
DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_NOTHROW
long dispatch_semaphore_signal(dispatch_semaphore_t dsema);
- 对计数信号量
+1
,假设之前的信号量小于0
,该函数会唤醒「一个」等候中的线程
- 参数不能为
NULL
- 回来值:假设该函数唤醒了一个线程,回来
非0
,不然回来0
函数完结:
long dispatch_semaphore_signal(dispatch_semaphore_t dsema)
{
long value = os_atomic_inc2o(dsema, dsema_value, release);
if (likely(value > 0)) {
return 0;
}
if (unlikely(value == LONG_MIN)) {
DISPATCH_CLIENT_CRASH(value,
"Unbalanced call to dispatch_semaphore_signal()");
}
return _dispatch_semaphore_signal_slow(dsema);
}
- 释放锁,一同对信号量
+1
- 假设假设信号量大于
0
,直接回来,
- 假设信号量
<= 0
,说明之前有线程在休眠等候信号,调用_dispatch_semaphore_signal_slow()
宣告一个信号来唤醒一个线程
4.总结
- 所谓等候的线程,是指调用
dispatch_semaphore_wait()
时地点的线程
- 从其他线程宣告信号
dispatch_semaphore_signal
,会唤醒一个等在中的线程
-
GCD
的dispatch_semaphore
就是一个计数信号量,通过这个计数量来处理线程,使线程或休眠等候,或唤醒执行任务
GCD
的信号量是对系统内核信号量的一层封装,要想更深化的了解,能够去研讨一下Linux
内核的信号量。
截止到本篇文章,一共分析了iOS
中7中锁,最终借用网络上盛行的一张图(对比了iOS中所有锁的功能):

图中有九种锁:
-
OSSpinLock
已被苹果抛弃,取而代之的是os_unfair_lock
-
pthread_mutex
和pthread_mutex(recursive)
归于pthread
范畴
- 除以上三种底层锁外,图中剩余的锁悉数都在本专栏文章中介绍了
用法
以及通过源码分析
研讨了完结原理