前言:
在《由浅至深理解iOS GCD (一) -- Semaphore的基本概念和使用》中我们也已经介绍了Semaphore的基本介绍和用法,我们在本章中讨论下Semaphore的原理及实现
原理及Apple的实现
说到信号量,我就想到unix系统就本身提供了相应的库,Apple会不会只是封装了一下。带着这样的疑问,我查询了Apple的源代码。
我们先来看 dispatch_semaphore_t 的定义
#if USE_MACH_SEM
#define DISPATCH_OS_SEMA_FIELD(base) semaphore_t base##_port
#elif USE_POSIX_SEM
#define DISPATCH_OS_SEMA_FIELD(base) sem_t base##_sem
#elif USE_WIN32_SEM
#define DISPATCH_OS_SEMA_FIELD(base) HANDLE base##_handle
#else
#error "No supported semaphore type"
#endif
#define DISPATCH_SEMAPHORE_HEADER(cls, ns) \
DISPATCH_OBJECT_HEADER(cls); \
long volatile ns##_value; \
DISPATCH_OS_SEMA_FIELD(ns)
struct dispatch_semaphore_header_s {
DISPATCH_SEMAPHORE_HEADER(semaphore, dsema);
};
DISPATCH_CLASS_DECL(semaphore);
struct dispatch_semaphore_s {
DISPATCH_SEMAPHORE_HEADER(semaphore, dsema);
long dsema_orig;
};
从上面代码中也印证了我最初的想法。从定义中就是可以肯定,Apple就是通过系统的信号量库实现的。只不过是在外面又包装了一层,支持多系统(MAC,POSIX,Win32)和ARC。 Mach是用semaphore_t实现的,POSIX使用sem_t实现的,windows使用HANDLE实现的。
既然知道了这个,那代码就再好懂不过了,我们先来看看dispatch_semaphore_signal的实现, dispatch_semaphore_signal的核心代码是在_dispatch_semaphore_signal_slow中实现的,所以这里就只放_dispatch_semaphore_signal_slow的代码
long
_dispatch_semaphore_signal_slow(dispatch_semaphore_t dsema)
{
#if USE_MACH_SEM
_dispatch_semaphore_create_port(&dsema->dsema_port);
kern_return_t kr = semaphore_signal(dsema->dsema_port);
DISPATCH_SEMAPHORE_VERIFY_KR(kr);
#elif USE_POSIX_SEM
int ret = sem_post(&dsema->dsema_sem);
DISPATCH_SEMAPHORE_VERIFY_RET(ret);
#elif USE_WIN32_SEM
_dispatch_semaphore_create_handle(&dsema->dsema_handle);
int ret = ReleaseSemaphore(dsema->dsema_handle, 1, NULL);
dispatch_assume(ret);
#endif
return 1;
}
其中最核心的代码就是这句
kern_return_t kr = semaphore_signal(dsema->dsema_port);
利用系统的信号量库实现发送信号量的功能。
接下来我们再来看看dispatch_semaphore_wait的实现。因为dispatch_semaphore_wait的实现的核心代码在_dispatch_semaphore_wait_slow中实现的,然而_dispatch_semaphore_wait_slow因为考虑到多系统,所以代码略长,为了节约篇幅,这里就是放跟Mach相关的代码,如果不巧你也想研究其他系统是怎么实现的,你可以参考_dispatch_semaphore_signal_slow中其他系统的实现举一反三啦(😂)。
static long
_dispatch_semaphore_wait_slow(dispatch_semaphore_t dsema,
dispatch_time_t timeout)
{
long orig;
#if USE_MACH_SEM
mach_timespec_t _timeout;
kern_return_t kr;
#endif
#if USE_MACH_SEM
_dispatch_semaphore_create_port(&dsema->dsema_port);
#endif
switch (timeout) {
default:
#if USE_MACH_SEM
do {
uint64_t nsec = _dispatch_timeout(timeout);
_timeout.tv_sec = (typeof(_timeout.tv_sec))(nsec / NSEC_PER_SEC);
_timeout.tv_nsec = (typeof(_timeout.tv_nsec))(nsec % NSEC_PER_SEC);
kr = slowpath(semaphore_timedwait(dsema->dsema_port, _timeout));
} while (kr == KERN_ABORTED);
if (kr != KERN_OPERATION_TIMED_OUT) {
DISPATCH_SEMAPHORE_VERIFY_KR(kr);
break;
}
#endif
// 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)) {
#if USE_MACH_SEM
return KERN_OPERATION_TIMED_OUT;
#endif
}
}
// Another thread called semaphore_signal().
// Fall through and drain the wakeup.
case DISPATCH_TIME_FOREVER:
#if USE_MACH_SEM
do {
kr = semaphore_wait(dsema->dsema_port);
} while (kr == KERN_ABORTED);
DISPATCH_SEMAPHORE_VERIFY_KR(kr);
#endif
break;
}
return 0;
}
其中核心的代码就是这两句
/* 有超时的代码 */
kr = slowpath(semaphore_timedwait(dsema->dsema_port, _timeout));
/* 没有超时的代码 */
kr = semaphore_wait(dsema->dsema_port);
写到这里GCD Semaphore的神秘面纱已经基本揭开,我们已经可以自己动手写一个Semaphore的库,使用include <semaphore.h>。
扩展阅读:基于pthread的Semaphore
当我想自己动手写一个类似的库时,我发现已经没有什么难度了。我突然想到也可以用pthread中互斥锁来实现类似的功能,所以我试着用C语言去实现一个Semaphore。代码如下:
#include <stdlib.h>
#include <pthread.h>
typedef struct semaphore {
pthread_cond_t cond;
pthread_mutex_t mutex;
int volatile value;
} semaphore_t;
static int dispatch_semaphore_init(semaphore_t *semaphore, int value)
{
if(value < 0){
fprintf(stderr, "semaphore_init():semaphore can take only value >= 0\n");
printf("semaphore_init():semaphore can take only value >= 0\n");
return -1;
}
pthread_mutex_init(&(semaphore->mutex), NULL);
pthread_cond_init(&(semaphore->cond), NULL);
semaphore->value = value;
return 0;
}
static void dispatch_semaphore_signal(semaphore_t *semaphore)
{
pthread_mutex_lock(&(semaphore->mutex));
if(semaphore->value <= 0){
pthread_cond_signal(&(semaphore->cond));
}
semaphore->value ++;
pthread_mutex_unlock(&(semaphore->mutex));
}
static void dispatch_semaphore_signal_all(semaphore_t *semaphore)
{
pthread_mutex_lock(&(semaphore->mutex));
semaphore->value = 0;
pthread_cond_broadcast(&(semaphore->cond));
pthread_mutex_unlock(&(semaphore->mutex));
}
static void dispatch_semaphore_wait(semaphore_t *semaphore)
{
pthread_mutex_lock(&(semaphore->mutex));
if(semaphore->value == 0){
pthread_cond_wait(&(semaphore->cond), &(semaphore->mutex));
}
semaphore->value --;
pthread_mutex_unlock(&(semaphore->mutex));
}
static int dispatch_semaphore_timedwait(semaphore_t *semaphore, long sec)
{
pthread_mutex_lock(&(semaphore->mutex));
int res = 0;
if(semaphore->value == 0){
struct timespec tv;
clock_gettime(CLOCK_MONOTONIC, &tv);
tv.tv_sec += sec;
res = pthread_cond_timedwait(&(semaphore->cond), &(semaphore->mutex), &tv);
}
semaphore->value --;
pthread_mutex_unlock(&(semaphore->mutex));
return res;
}
从上面的代码中最核心的代码就是使用系统的pthread库, 先创建一个互斥锁 和条件变量。
pthread_mutex_init(&(semaphore->mutex), NULL);
pthread_cond_init(&(semaphore->cond), NULL);
semaphore->value = value;
通过使用pthread 条件变量的wait和signal来实现semaphore_wait 和semaphore_signal
static void dispatch_semaphore_signal(semaphore_t *semaphore)
{
pthread_mutex_lock(&(semaphore->mutex));
if(semaphore->value <= 0){
pthread_cond_signal(&(semaphore->cond));
}
semaphore->value ++;
pthread_mutex_unlock(&(semaphore->mutex));
}
static void dispatch_semaphore_wait(semaphore_t *semaphore)
{
pthread_mutex_lock(&(semaphore->mutex));
if(semaphore->value == 0){
pthread_cond_wait(&(semaphore->cond), &(semaphore->mutex));
}
semaphore->value --;
pthread_mutex_unlock(&(semaphore->mutex));
}
虽然代码很简单,但是还是实现了GCD Semaphore的功能。