陈硕Linux多线程服务端编程读书笔记

思维导图

(新窗口打开可以看大图)

一些收获

条件变量的虚假唤醒(spurious wakeup)

使用条件变量,可以让线程等待某个条件,从而进入睡眠,当由其他线程所控制的布尔表达式满足条件时,再由操作系统将其唤醒。条件变量的使用需要搭配一个互斥器,有一套几乎不变的范式

// wait端
Mutex mutex;
Condition cond(mutex);
{
    MutexLockGuard lock(mutex);
    while(bool-expression)
    {
        cond.wait();        // 1. 执行wait()发生了什么?
    }
    // some operation after wake up
}

//2. wait端改成这样可以吗:
{
    if(bool-expression)
    {
        cond.wait();
    }
    // some operation after wake up
}
// signal端
Mutex mutex;
{
    MutexLockGuard lock(mutex);
    modity-bool-expression;
    cond.notify();
}

上面的代码提出了两个问题。先说问题1,条件变量在初始化时,就和一个互斥量绑定(注意这个互斥量既被用于MutexLockGuard 保护布尔表达式也用于与Condition绑定)。进入wait()后,条件变量会释放已经获取到的锁,然后使当前线程进入睡眠(当然释放是必须的,要是不释放就陷入睡眠,那这个布尔表达式永远也得不到修改)。生产者线程此时可以获取锁,并且改变这个布尔表达式,改变结束后将调用cond.notify(),唤醒消费者。消费者此时还在wait()中但即将返回,wait()函数在返回时,需要重新获取锁以获得对布尔表达式的独占。
再说问题2,使用if判断是不行的,使用while循环的原因就是所谓的防止虚假唤醒。直观的来讲,条件变量的使用场景可能让人无意有了“一个消费者,一个生产者”的定势(因为这操作系统教科书里的经典例子)。
当消费者和生产者的数量都为1的时候,那么使用if语句是没有问题的。但考虑“一个生产者,多个消费者”的情况,使用if语句判断当由其他线程所控制的布尔表达式就会出现问题:如果此时线程A、B同时被唤醒,A线程先被唤醒,B线程后被唤醒。B线程已经抢先一步修改了布尔表达式,执行了相应逻辑,但此时线程A可能刚刚从wait()中返回,便在没有检查“最新情况下”的布尔表达式的情况下,错误的执行不该执行的逻辑。视布尔表达式的情况,可能会导致程序crash(比如布尔表达式是vector的empty(),就会造成内存错误)。

条件变量解决了什么问题

一言以蔽之,由轮询转化为被动等待。

不使用条件变量的情况:

// Thread Producer
produce () {
    mutex_lock()
    count++
    mutex_unlock()    
}

// Thread A
celebrateAfter100 () {
    while (1) {
        mutex_lock()
        if (count >= 100) {
            mutex_unlock()
            break
        }
        mutex_unlock()
        sleep(100)
    }
    // Celebrate!
}

使用条件变量的情况:

// Thread Producer
produce () {
    mutex_lock()
    count++
    mutex_unlock()    
    
    if (count >= 100)
        cond_signal(condition)  // 条件满足啦,通知一个等待的线程
}


// Thread A
celebrateAfter100 () {
    mutex_lock()
    while(count < 100)
        cond_wait(condition)    // 等到条件满足再继续执行
    mutex_unlock()
    // Celebrate!
}

Double-Check-Lock的单例问题在哪?

先要明确的一点,线程安全的单例模式只关注获取资源的时候是否存在race condition,比如这样的代码:

Instance *GetInstance()
{
    if(!p)
        p = new Instance();
    return p;
}

在多线程环境下是不安全的,可能导致内存泄露,但是线程安全的单例模式并不关注线程安全地获取资源(唯一实例的地址)后,实例的成员的线程安全问题
经典的单例有很多种,饿汉式/懒汉式(这名字可真是取的……一言难尽,就不能文雅一点吗?)。
饿汉式单例也就是Meyer's Singleton模式,虽然不同编译单元中,非local static变量存在初始化顺序不确定的问题(Effective C++ 条款4),但是对于函数内部的static变量,C++可以保证在函数调用时,内部的static变量必被初始化,而这个资源的获取是线程安全的,所以Meyer's Singleton可以放心的返回local static变量的地址或引用,实例如下:

Instance *GetInstance()
{
    static Instance instance;
    return &instance;
}

Copy-On-Write(写时复制)手法

posted @ 2020-02-17 21:26  joeyzzz  阅读(1033)  评论(0编辑  收藏  举报