PDF 及笔记:
电脑上如果 PDF 不展示或者展示不正常,使用 Chrome 并安装 PDF Viewer 插件。其他情况请下载文件查看:tlpi-ch29。
部分笔记
Linux 对 POSIX pthread 标准的实现
POSIX 标准定义了一套线程机制和 API 标准,称为 pthread。
Linux 实现了 pthread 标准,该实现被称为 NPTL(Native POSIX Threads Library)。旧的 Linux 版本的实现为 LinuxThreads,目前已不主流。
多线程程序的内存布局
pthread API 的返回值风格
一般系统调用的 API,其返回值为 0 表示成功、-1 表示失败;具体失败原因从 errno 中取。
pthread API 不同,0 表示成功,正整数表示失败,且其值同 errno 定义的一致。
不同线程拥有自己的 errno。在 Linux 上,读取 errno 实际上是通过一段 macro 将其转化为一个函数调用(而不是读一个全局变量)。
线程间共享和不共享的数据
Besides global memory, threads also share a number of other attributes (i.e., these attributes are global to a process, rather than specific to a thread). These attributes include the following:
- process ID and parent process ID;
- process group ID and session ID;
- controlling terminal;
- process credentials (user and group IDs);
- open file descriptors;
- record locks created using fcntl(); z signal dispositions;
- file system–related information: umask, current working directory, and root directory;
- interval timers (setitimer()) and POSIX timers (timer_create());
- System V semaphore undo (semadj) values (Section 47.8);
- resource limits;
- CPU time consumed (as returned by times());
- resources consumed (as returned by getrusage()); and
- nice value (set by setpriority() and nice()).
Among the attributes that are distinct for each thread are the following:
- thread ID (Section 29.5);
- signal mask;
- thread-specific data (Section 31.3);
- alternate signal stack (sigaltstack());
- the errno variable;
- floating-point environment (see fenv(3));
- realtime scheduling policy and priority (Sections 35.2 and 35.3);
- CPU affinity (Linux-specific, described in Section 35.4);
- capabilities (Linux-specific, described in Chapter 39); and
- stack (local variables and function call linkage information)
线程结束自身运行的数种方式
- The thread’s start function performs a return specifying a return value for the thread.
- The thread calls pthread_exit() (described below).
- The thread is canceled using pthread_cancel() (described in Section 32.1).
- Any of the threads calls exit(), or the main thread performs a return (in the main() function), which causes all threads in the process to terminate immediately
值得注意的是最后一种方式,无论哪个线程调用了 exit()
,整个程序、所有线程都会马上结束。
编码考虑点
调用完 pthread_create() 后,操作系统并不保证马上调度到该线程运行。
线程执行完后,其返回值(一般是指针)不该指向线程的栈。因为这块内存区域在线程结束后会被 OS 回收利用。
线程间等待结束的机制
线程间等待结束和回收资源 pthread_join()
与进程间 waitpid()
不同之处:
- 不同线程之间是对等关系,任意线程可以调用
pthread_join()
等待其他任意进程结束;进程间则需要是父进程回收子进程 - 由于线程间并没有父子关系,pthread 没有提供一个可以 join 任意线程的 API(进程则有)
- 如果可以 join 任意线程,可能会把当前进程使用的第三方库的线程也 join 了,引起混乱
- pthread 设计上希望线程只 join 自己知道(known)的其他线程
- 但是 pthread 提供了 condition variables 机制可以模拟这一效果
线程与进程对比
章末总结的线程与进程对比,是本章的 重点。
- 共享数据:
- 线程比进程容易;进程需要额外的 IPC 代码
- 线程间读写数据,需要额外的同步机制,避免同时写同块数据
- 创建和调度成本:线程低一个数量级
- 使用第三方库的考虑:多线程时需要开发者确认使用的库是否是线程安全的;如果不是,需要以线程安全的方式调用它。多进程没有此问题
- 线程间共享虚拟内存地址带来的问题:
- 如果一个线程出现 bug,错误地修改了另外一个线程的内存,可能引起很难定位的 bug;进程无此问题
- 同一进程的多个线程,其所使用的虚拟内存总和不能超过单进程的限制(比如 x86-32 中的 3G);而多进程时,每个进程都能使用 3G。当然在 64 位系统普遍的今天不是什么大问题
- 多线程在其他编程上的考虑点:
- 信号处理:很难正确的处理,因此多线程程序一般不配合使用信号机制
- 多线程运行的代码只能来自一个可执行程序(尽管可能是不同的函数);多进程则可以运行不同程序
- 多线程还共享了 fd、当前工作目录、user / group ID 等;视情况可能是优点也可能是缺点