关键词:pthread_create()、pthread_exit()、pthread_self()、pthread_join()、pthread_detach()、pthread_attr_init()、pthread_attr_destroy()等等。
本章首先介绍了线程在进程中内存属性,然后介绍了统一进程中线程间共享属性,以及线程私有属性。
然后介绍了如何创建线程、终止线程、获取线程ID、连接线程、分离线程,以及如何设置线程属性。
最后一个应用实现为一组线程还是一组进程进行了对比。
1. 概述
一个进程包含多个线程,并均会独立并发执行相同程序,共享同一份全局内存区域,包括代码段、初始化数据段、为初始化数据段、堆;但是每个线程都有自己独立的栈。
创建线程相对于创建进程的优点:
- 进程间信息难以共享。必须通过进程间通信方式,在进程间交换信息。
- 调用fork()创建进程代价较高。需要复制页表、文件描述符表等多进程属性。
线程共享的属性还包括:
进程ID和父进程ID、进程组ID和会话ID、控制终端、进程凭证、打开的文件描述符、fcntl()创建的记录锁、信号处置、文件系统相关信息、setitimer()和timer_create()、systemV信号量、资源限制、CPU时间消耗、资源消耗、nice值。
线程独有属性包括:
线程ID、信号掩码、备选信号栈、errno变量、浮点型环境、实时调度策略和优先级、CPU亲和力、能力、栈、本地变量和函数调用链接。
2. Pthreads API的详细背景
线程数据类型:
Linux多线程环境中,每个线程都有自己的errno。
Pthreads函数返回值,0表示成功,正值表示失败。
调用Pthreads API进行编程时,需要-lpthread进行链接。
3. 创建线程
函数pthread_create()负责创建新的线程:
#include <pthread.h> int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start)(void *), void *arg); Returns 0 on success, or a positive error number on error
新线程通过调用带有参数arg的函数start而开始执行。
arg为void*类型,可以执行一个全局变量或堆变量、或NULL、或一个结构体指针。
start()返回值类型为vod*。
thread指向pthread_t类型的指针,后续Pthreads函数将使用该标识来引用该线程。
attr指向pthread_attr_t对象的指针,该对象定义了新线程的各种属性。
线程创建后,应用程序无法确定系统会接着调度哪一个线程来执行。
4. 终止线程
终止线程的方式:
- 线程函数中执行return语句并返回指定值。
- 线程调用pthread_exit()。
- 调用pthread_cancel()取消线程。
- 任意线程调用了exit(),或主线程执行了return语句,都会导致进程中所有线程立即终止。
pthread_exit()将终止调用线程,且返回值可由另一线程通过调用pthread_join()来获取:
include <pthread.h> void pthread_exit(void *retval);
在线程函数调用的任意函数中调用pthread_exit()则终止当前线程。
retval指定了线程的返回值。retval所指向的内容不应分配于线程栈中。
如果主线程调用了pthread_exit(),而非exit()或return语句,那其他线程将继续执行。
5. 线程ID
线程可以通过pthread_self()来获取自己的线程ID:
include <pthread.h> pthread_t pthread_self(void); Returns the thread ID of the calling thread
获取线程ID有以下作用:
- pthread_join()、pthread_detach()、pthread_cancel()、pthread_kill需要线程ID来操作目标线程。
- 以特定线程ID作为动态数据结构的标签。
pthread_equal()可检查两个线程的ID是否相同:
include <pthread.h> int pthread_equal(pthread_t t1, pthread_t t2); Returns nonzero value if t1 and t2 are equal, otherwise 0
6. 连接已终止的线程
pthread_join()等待由thread标识的线程终止:
include <pthread.h> int pthread_join(pthread_t thread, void **retval); Returns 0 on success, or a positive error number on error
retval为一非空指针,保存线程终止时返回值的拷贝,该返回值亦即线程调用return或pthread_exit()时所制定的值。
如果pthread_join()传入一个之前已然连接过的线程ID,将导致无法预知的行为。
若线程未分离,则必须使用pthread_join()来进行连接。如未能连接,则线程终止时将产生僵尸线程。
pthread_join()和waitpid()区别:
- 线程之间的关系是对等的。进程中任意线程均可以调用pthread_join()与该进程中任何其他线程链接起来。
- pthread_join()无法一次连接任意线程,也不能以非阻塞方式进行阻塞。使用条件变量可以实现类似的功能。waitpid(-1,&status, options)可以一次连接任意进程。
#include <pthread.h> #include "tlpi_hdr.h" static void * threadFunc(void *arg) { char *s = (char *) arg; printf("%s", s); return (void *) strlen(s); } int main(int argc, char *argv[]) { pthread_t t1; void *res; int s; s = pthread_create(&t1, NULL, threadFunc, "Hello world "); if (s != 0) errExitEN(s, "pthread_create"); printf("Message from main() "); s = pthread_join(t1, &res); if (s != 0) errExitEN(s, "pthread_join"); printf("Thread returned %ld ", (long) res); exit(EXIT_SUCCESS); }
7. 线程的分离
pthread_detach()将thread指定的线程标记为处于分离状态:
#include <pthread.h> int pthread_detach(pthread_t thread); Returns 0 on success, or a positive error number on error
pthread_detach(pthread_self())线程可以自行分离。
一旦线程处于分离状态,就不能再用pthread_join()获取状态,也无法使其返回可连接状态。
其他线程调用了exit()或主线程return,即便遭分离的线程还是会受影响,所有线程会立即终止。
8. 线程属性
pthread_attr_init()对线程属性结构进行初始化,pthread_create()创建线程的时候使用,使用完后通过pthread_attr_destroy()进行销毁。
9. 线程VS进程
实际应用实现为一组线程还是进程,需要对比线程和进程。
首先看线程优点:
- 线程间的数据共享简单。
- 创建线程要快于创建进程。
线程缺点如下:
- 多线程编程时,需要确保调用线程安全的函数。
- 某个线程bug可能会危及该进程所有线程,因为他们共享相同地址空间以及其他属性。
- 每个线程都争用宿主进程中有限虚拟地址空间。每个线程栈和线程特有数据都小号进程虚拟地址空间一部分。
其他影响点包括:
- 多线程中处理信号,需要小心设计。多线程中避免使用信号。
- 多线程应用中,所有线程必须运行同一个程序。多进程应用,不同进程可以运行不同程序。
- 除了数据,线程还可以共享某些其他信息。