陈硕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;
}