并发编程有两种模型,一种是message passing,另一种是 shared memory。在分布式系统中,运行在多台机器上的多个进程的并行编程方式只有 message passing。
线程同步的4项原则,按重要性排序:
- 首先原则是尽量最低限度的共享对象,减少需要同步的场合。一个对象能不暴露给别的线程就不要暴露;如果要暴露,优先考虑immutable 对象;实在不行才暴露可修改的对象,并用同步措施来充分保护它。
- 其次是使用高级的并发编程构件,如TaskQueue、Producer-Consumer Queue、CountDownLatch等等。
- 最后不得已必须使用同步底层原语时,只用非递归的互斥器和条件变量,慎用读写锁,不要用信号量。
- 除了使用atomic整数外,不自己编写lock-free代码,也不要用“内核级”同步原语。不凭空猜测“哪种做法会更好”。
互斥器(mutex)
互斥器(mutex)恐怕是使用得最多的同步原语,粗略地说,它保护了临界区,任何一个时刻最多只能有一个线程在此mutex划出的临界区内活动。单独使用mutex时,我们主要为了保护共享数据。作者的原则是:
- 用RAII手法封装mutex创建、销毁、加锁、解锁这四个操作。用RAII封装这几个操作是通行的做法,这几乎是C++的标准实践,保证锁的生效期间等于一个作用域,不会因异常而忘记解锁。
- 只用非递归的mutex,即不可重入的mutex。
- 不手工调用lock()和unlock()函数,一切交给栈上的Guard对象的析构和析构函数负责。
- 在每次构造Guard对象的时候,思考一路上已经持有的锁,防止因加锁顺序不同而导致死锁。
次要原则有:
- 不使用跨进程的mutex,进程间通信只用TCP sockets。
- 加锁、解锁在同一个线程。
- 别忘了解锁(RAII自动保证)
- 不要重复解锁(RAII自动保证)
- 必要的时候可以考虑使用PTHREAD_MUTEX_ERRORCHECK 来排错。
只使用非递归的mutex
mutex 分为递归和非递归两种,这是POSIX的叫法,另外的名字叫做可重入和非可重入。