互斥量、信号量、条件变量,三者之间的互相实现

这三者是多任务编程中最常见的基础设施,而它们背后隐藏着更深层的关系。例如,它们全都可以用信号量来实现;如果没有信号量,也可以用互斥量和条件变量组合实现一个信号量……

namespace bigcat {
    class mutex {
    private:
        semaphore sem = semaphore(1);
    public:
        void wait() {
            sem.wait();
        }
        void notify() {
            sem.notify();
        }
    };
    
    class semaphore {
    private:
        condition_variable cv;
        mutex mtx;
        unsigned int count;
    public:
        semaphore(unsigned int n) : count(n) {};
        void wait() {
            mtx.lock();
            while(!count) cv.wait(mtx);
            count--;
            mtx.unlock();
        }
        void notify() {
            mtx.lock();
            count++;
            mtx.unlock();
            cv.notify_all();
        }
    };
    
    class condition_variable {
    private:
        semaphore sem = semaphore(0);
        mutex mtx_count;
        unsigned int wait_count = 0;
    public:
        void wait(mutex &mtx) {
            mtx.unlock();
            mtx_count.lock();
            wait_count++;
            mtx_count.unlock();
            sem.wait();
            mtx.lock();
        }
        void notify() {
            mtx_count.lock();
            if(wait_count) {
                wait_count--;
            }
            sem.notify();
            mtx_count.unlock();
        }
        void notify_all() {
            mtx_count.lock();
            for(; wait_count; wait_count--) {
                sem.notify();
            }
            mtx_count.unlock();
        }
    };
}

值一提的是:C++20 以前(不包含 C++20)的线程相关库并不支持信号量,于是可以通过这种形式来实现。最简单快捷的方法还是直接使用系统的 API,但这会降低可移植性,需要做出一定取舍。

  • 信号量→互斥量
  • 互斥量+信号量→条件变量
  • 条件变量+互斥量→信号量

如果只允许操作系统实现三种其中一种,那么显然单独信号量就足以实现其他两种设施。这种意义上信号量确实是最基础的设施。信号量从直观上是一个计数器加上一个“休眠本任务/唤醒其它任务”的机制。它是系统内核中的任务调度控制机构在用户层的最简单体现。