本篇文章从通过源码分析来研讨其完结原理。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,会唤醒一个等在中的线程
  • GCDdispatch_semaphore就是一个计数信号量,通过这个计数量来处理线程,使线程或休眠等候,或唤醒执行任务

GCD的信号量是对系统内核信号量的一层封装,要想更深化的了解,能够去研讨一下Linux内核的信号量。

截止到本篇文章,一共分析了iOS中7中锁,最终借用网络上盛行的一张图(对比了iOS中所有锁的功能):

学会了iOS信号量dispatch_semaphore线程锁,你用起来称心如意!!!

图中有九种锁:

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