Swoole 中对于线程锁的封装位于 'src/lock' 目录,很明了地实现了互斥锁、读写锁、自旋锁三种,结构非常简单。

整体结构

从使用上看, Swoole 将锁操作统一为有限几种(不管锁的原始语义支持不支持),并写为抽象基类中的纯虚函数,实现类似接口的效果。具体的子类继承并实现对应加锁、释放锁的系列操作。

抽象基类

抽象基类 Lock 全贴进来也没几行,除了上面提到的还记录了锁类型和是否进程间共享等信息。

class Lock {
  public:
    enum Type {
        NONE,
        RW_LOCK = 1,
        FILE_LOCK = 2,
        MUTEX = 3,
        SEM = 4,
        SPIN_LOCK = 5,
        ATOMIC_LOCK = 6,
    };
    Type get_type() {
        return type_;
    }
    virtual ~Lock(){};
    virtual int lock_rd() = 0;
    virtual int lock() = 0;
    virtual int unlock() = 0;
    virtual int trylock_rd() = 0;
    virtual int trylock() = 0;

  protected:
    Lock() {
        type_ = NONE;
        shared_ = false;
    }
    enum Type type_;
    bool shared_;
};

实现类的特殊成员

从设计上讲,子类继承并实现纯虚函数。但不同子类具体实现细节是有区别的,可能需要借助一些自定义数据、操作。而 Swoole 的处理方式是子类增加一个 XxxImpl 类型的成员指针 XxxImpl *impl 。在 Swoole 中,很多模块都有可见类似的操作。以 Mutex 为例:

classDiagram direction RL class Lock { enum Type type_; bool shared_; virtual ~Lock(); virtual int lock_rd() = 0; virtual int lock() = 0; virtual int unlock() = 0; virtual int trylock_rd() = 0; virtual int trylock() = 0; } class Mutex { + MutexImpl *impl; } Mutex --|> Lock : 实现抽象基类

至于子类 Mutex 中增加的 MutexImpl *impl 成员,很显然就是子类所需的特定数据或方法,当然这里 Mutex 只需要互斥锁及其属性数据:

struct MutexImpl {
    pthread_mutex_t lock_;
    pthread_mutexattr_t attr_;
};

几种具体实现

三者在初始化时,都会根据是否共享设定相应的 pthread_***attr_t ,其保存于子类中增加的 MutexImpl *impl 成员。这个成员只是一个指针,初始化时会动态申请内存并在子类析构时释放。相关内容可见于: Swoole 源码分析3 - 动态内存管理 ,现在可以先暂时不管这方面细节。

互斥锁

对互斥锁而言是没有所谓获取读锁这种操作的, Swoole 的处理方式是 lock_rd()trylock_rd() 其实都是获取互斥锁,分别对应 lock()trylock() (相当于这两个方法的别名),最后对应调用 pthread_mutex_***() 系列函数。唯一特别的是 Mutex 单独有个针对 pthread_mutex_timedlock() 的封装,其他两个都没有封装对应的操作。

#ifdef HAVE_MUTEX_TIMEDLOCK
int Mutex::lock_wait(int timeout_msec) {
    struct timespec timeo = swoole_time_until(timeout_msec);
    return pthread_mutex_timedlock(&impl->lock_, &timeo);
}
#else
int Mutex::lock_wait(int timeout_msec) {
    int sub = 1;
    int sleep_ms = 1000;

    if (timeout_msec > 100) {
        sub = 10;
        sleep_ms = 10000;
    }

    while (timeout_msec > 0) {
        if (pthread_mutex_trylock(&impl->lock_) == 0) {
            return 0;
        } else {
            usleep(sleep_ms);
            timeout_msec -= sub;
        }
    }
    return ETIMEDOUT;
}
#endif

可见在系统不直接支持的情形下, lock_wait() 退而求其次的实现是循环调用 pthread_mutex_trylock()usleep() ,直到设定的超时时间或者获取到锁。

读写锁

对应于抽象基类,读写锁的语义很完整,获取写锁对应于抽象基类中的 lock() 获取锁,其他如获取读锁等则与 pthread_rwlock_***lock() 系列方法一一对应。当然,按照设计,子类 RWLock 中增加的 XxxImpl 成员即为其所需特殊数据或方法:

struct RWLockImpl {
    pthread_rwlock_t _lock;
    pthread_rwlockattr_t attr;
};

自旋锁

与互斥锁一样,自旋锁没有获取读锁的语义, lock_rd()trylock_rd() 也相当于别名,其他的完全类似。因为 linux 对于 spinlock 没有所谓的属性值,所以子类 SpinLock 的 impl 成员最简单:

class SpinLock : public Lock {
    pthread_spinlock_t *impl;

    // ......
};

总结

只有非常少量的代码,做了简单的封装。从设计上看并没有设计为所谓的 RAII 模式。目前源码还没有看完,但看到在某些 RAII 作用很大的地方,直接使用的标准库的锁,如 std::unique_lock