好长一段时间没有写文章了,这次总结一下陈硕大大写的《Linux多线程服务端编程》一书第二章的读书笔记。
1.互斥器(mutex)
使用互斥器的基本原则:
- 用RAII手法封装mutex的创建、销毁、加锁、解锁这四个操作。Java里面的synchronized语句和C#的using语句也有类似的效果,即保证所得生效期间等于一个作用于,不会因异常而忘记解锁。
- 只用非递归的mutex(即不可重入的mutext)
- 不手工调用lock()和unlock()函数,一切交给栈上的Guard对象的构造和析构函数负责。
- 在每次构造Guard对象的时候,思考一路上(调用栈上)已经持有的锁,防止因为锁顺序不同而导致死锁。
- 进程间通信使用socket,不要使用mutex。
- 加锁、解锁在同一个线程(RAII会保证)。
- 别忘记解锁(RAII会保证)。
- 不要重复解锁(RAII会保证)。
- 必要的时候可以考虑用PTHREAD_MUTEX_ERRORCHECK来排错。
基本的互斥器的代码如下:
class MutexLock {
public:
MutexLock() {
pthread_mutex_init(&_mutex, NULL);
}
~MutexLock() {
pthread_mutex_destroy(&_mutex);
}
MutexLock& operator=(MutexLock& other) = delete;
MutexLock(MutexLock& other) = delete;
void lock() {
pthread_mutex_lock(&_mutex);
}
void unlock() {
pthread_mutex_unlock(&_mutex);
}
pthread_mutex_t* getRawMutex() {
return &_mutex;
}
private:
pthread_mutex_t _mutex;
};
class MutexGuard {
public:
MutexGuard(MutexLock& mutex)
:_lock(mutex) {
_lock.lock();
}
~MutexGuard() {
_lock.unlock();
}
private:
MutexLock& _lock;
};
2.条件变量(condition variable)
如果需要等待某个条件成立,我们应该使用条件变量。条件变量顾名思义是一个或多个线程等待某个布尔表达式为真,即等待别的线程“唤醒”它。条件变量的学名叫管程(monitor)。Java Object内置的wait()、notify()、notifyAll()是条件变量。
对于wait端应该注意:
- 必须与mutex一起使用,该布尔表达式的读写接收此mutex的保护
- 在mutex已经上锁的时候才能调用wait()
- 把判断布尔条件和wait()放到while循环中
如下是一个简单的Condition实现
class Condition {
public:
Condition(MutexLock& mutex)
:_mutex(mutex) {
pthread_cond_init(&_cond, NULL);
}
~Condition() {
pthread_cond_destroy(&_cond);
}
void wait() {
pthread_cond_wait(&_cond, _mutex.getRawMutex());
}
void notify() {
pthread_cond_signal(&_cond);
}
void notifyAll() {
pthread_cond_broadcast(&_cond);
}
private:
MutexLock& _mutex;
pthread_cond_t _cond;
};
关于Spurious wakeup(虚假唤醒)
虚假唤醒在linux的多处理器系统中,在程序接收到信号前可能会发生。在Windows系统和JAVA虚拟机上也存在。在系统设计时应该可以避免虚假唤醒,但是这会影响条件变量的执行效率,而既然通过while循环就能避免虚假唤醒造成的错误,因此程序的逻辑就变成了while循环的情况。
3.copy-on-write
主要思想是这样的:让读的加锁粒度尽量的小,但当写数据的时候,新的读请求是阻塞的。
typedef std::shared_ptr<Map> MapPtr;
MapPtr gData(new Map); // 有一个数据是需要共享的
MapPtr getData() { // 读数据只有这一段是加锁的
MutexGuard guard(mutex);
return gData;
}
// read threads
MapPtr _data = getData();
// process _data....
// write thread
MutexGuard guard(mutex); // 写数据全场加锁
if(!gData.unique()) {
MapPtr tmpData(new Map(gData.get()));
gData.swap(tmpData);
}
// process _data ...
4.小结
记得两年前开始了解C++多线程编程的时候看了陈硕的这本书,工作两年,由于长时间使用Python,对C++已经有些陌生了,更别说C++多线程编程了,今天再次拾起这本书,重读前面两章就当复习了。
接下来我会抽周末看一下《C++ Concurrency In Action》,也会陆续写一些相关的读书笔记。