《Linux多线程服务端编程》阅读笔记

2022-03-23

1 当一个对象能被多个线程同时看到时,那么这个对象的销毁时机就会变得模糊不清,可能出现多种竞态条件:

解决这些race condtion是C++多线程编程面临的基本问题。可以使用shared_ptr来一劳永逸地解决这些问题。

2 对象构造要做到线程安全,唯一的要求是构造期间不能泄漏this指针。

基于此,二段式构造--即构造函数 + initialize() -- 有时会是好办法。这种方式虽然不符合C++教条,但是多线程下别无选择。

3 mutex不能安全地保护析构,因为一旦执行析构,mutex对象也会被销毁。这样,在多线程的情况下,如果其他线程正在使用mutex用到一半,就会有问题。

4 空悬指针:

两个指针p1,p2,指向堆里的同一个对象Object,并且p1和p2位于不同的线程中(线程A和线程B)。假设线程A通过p1指针将对象销毁了,那么p2就成了悬空指针。这是一种典型的C/C++内存错误。

要想安全地销毁对象,最好在别人(即别的线程)都看不到的情况下,偷偷地做。这个也正是垃圾回收(gc)的原理,所有人用不到的一定是垃圾。

(悬空指针,是指指向的内容已经被释放的指针)

5 C++里可能出现的内存问题大致有如下几个方面:

而正确地使用智能指针可以很轻易地解决前5种问题

6 shared_ptr的拷贝开销要比原始指针的拷贝开销要高(因为拷贝的时候需要修改引用计数,而修改引用计数需要加锁操作)。所以,我们在将shared_ptr作为函数参数传递的时候,尽量使用常引用的形式,这样减少拷贝次数,来减少性能损失。

7 让this指针,能变身为shared_ptr的方法,是让类继承 enable_shared_from_this。

8 弱回调:如果对象还活着,就调用它的成员函数,否则忽略之。

9 read-copy-update

10 不推荐使用信用量(Semaphore),原因:

11 使用 pthread_once 来实现 Singleton

12 在"non-blocing IO + IO multiplexing" 这种模型(即Reactor模式)中,程序的基本结构是一个事件循环(event loop), 以事件驱动(event-driven) 和事件回调的方式实现业务逻辑。

Reactor摸型的优点:

编程不难,效率也不错。不仅可以用于读写socket,连接的建立,甚至DNS解析都可以用非阻塞的方式进行,以提高并发度和吞吐量,对于IO密集的应用是一个不错的选择。

缺点:

它要求事件回调函数必须是非阻塞的。对于涉及网络IO的请求响应式协议,它容易割裂业务逻辑,使其散布于多个回调函数之中,相对不容易理解和维护。

13 one loop per thread

libev的作者说:

One loop per thread is usually a good model. Doing this is almost never wrong, sometimes a better-performance model exists, but it is always a good start.

这种方式的好处,在于:

Eventloop代表了线程的主循环,需要让哪个线程干活,就把timer或IOchannel(如TCP连接)注册到哪个线程的loop里即可。

对实时性有要求的额connection 可以单独用一个线程;

数据量大的connection可以独占一个线程,并把数据处理任务分摊到另几个计算线程中(用线程池);

其他次要的辅助性connection可以共享一个线程。

对于具有一定规模的服务端程序,一般就会采用 non-blockong + IO multiplexing, 每个connection/acceptor 都会注册到某个eventloop上,程序里有多个event loop,每个线程至多有一个event loop。

多线程程序对event loop 提出了更高的要求,那就是“线程安全”。要允许一个线程往别的线程的loop里塞数据,这个loop必须得是线程安全的。

14 进程间通信首选Sockets(主要是指TCP),其最大的好处在于:可以跨主机,具有伸缩性。其他优势:

15 使用TCP长连接的好处有两点:

16 本书对 “服务器开发” 的定义,用一句话形容:

跑在多核机器上的Linux用户态的没有用户界面的长期运行的 网络应用程序,通常是分布式系统的组成部件。

17 多线程的适用场景时:提高响应速度,让IO和“计算”相互重叠,降低latency(延迟)。虽然多线程不能提高绝对性能,但是能提高平均响应性能。

一个程序要做成多线程的,大致要满足:

18 多线程服务程序中的线程大致可以分为三类:

19 Linux 能同时启动多少个线程?

对于32-bit Linux,一个进程的地址空间是4GB,其中用户态能访问的为3GB左右,而一个线程的默认栈大小是10 MB,简单计算,一个进程大约可以同时启动300个线程。

对于64-bit系统,线程数目可大大增加

20 尽管C++03标准没有明说标准库的线程安全性,但