×

由浅至深理解iOS GCD (二) -- Semaphore的原理和实现

96
MacPu
2017.01.03 19:10* 字数 633

前言:

在《由浅至深理解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的功能。

由浅至深理解iOS GCD
Web note ad 1