陈硕《Linux 多线程服务端编程:使用 muduo C++ 网络库》笔记摘要
陈硕《Linux 多线程服务端编程:使用 muduo C++ 网络库》笔记摘要
前言
尽可能用 message passing 模型,避免 shared memory 模型。因为前者保证程序正确性更容易,并且移植到分布式系统中更简单。
第二章 线程同步精要
四项原则:
- 首要原则是尽量最低限度的共享对象;
- 其次是使用高级的并发编程构件:如TaskQueue, Produce-Consumer Queue, CountDownLatch;
- 底层同步原语只使用非递归的互斥器和条件变量,慎用读写锁,不要用信号量;
- 除了使用atomic整数之外,不自觉编写lock-free代码;
作者的个人原则:
- 用RAII手法封装mutex的创建,销毁,加锁,解锁这4个操作;
- 只用非递归的mutex;
- 不手工调用lock()和unlock()函数;
- 不使用跨进程的mutex,进程间通信只用TCP sockets;
- 函数拆分重构,如果一个函数既可能在已经加锁的情况下调用,又可能在未加锁的情况下调用,那么就拆分成两个函数。
- 线程安全的Singleton实现,使用pthread_once。
线程的销毁有几种方式:
- 自然死亡。这是线程正常退出的方式。
- 非正常死亡。抛出异常,或者触发信号等非法操作;
- 自杀。调用pthread_exit()来退出。
- 他杀。调用pthread_candel()来强制终止某个线程。
只用非递归锁是因为滥用递归锁更容易发生死锁,且不容易发现;
读写锁的问题有:
- 正确性:程序员容易不小心在read lock保护的函数中调用修改状态的函数
- 性能:读写锁开销总是大于等于mutex
- 读锁如果可以提升为写锁,那么可能会造成可重入锁一样的问题
信号量的问题有: - semaphore has no notion of ownership
- 时刻保持信号量与我们自己数据结构长度值的一致,增加了程序猿的负担和出错的可能
copy-on-write & read-and-update
写时,可以先加锁拷贝变量副本,然后修改变量副本。读时,可以先临界区拷贝变量到函数局部变量,再操作函数局部变量来避免死锁。前者帮助避免修改正在读的数据,后者帮助避免死锁或者读正在修改的数据。