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

前言:

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,194评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,058评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,780评论 0 346
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,388评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,430评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,764评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,907评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,679评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,122评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,459评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,605评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,270评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,867评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,734评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,961评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,297评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,472评论 2 348

推荐阅读更多精彩内容