目录
早期Linux不支持线程,直到1996年,Xavier Leroy等人才开发出第一个基本符合POSIX标准的线程库Linux Threads。但LinuxThreads效率低而且问题很多。自内核2.6开始,Linux才真正提供内核级的线程支持,并有两个组织致力于编写新的线程库:NGPT(Next Generation POSIX Threads)和NPTL(Native POSIX Thread Library)。不过前者在2003年就放弃了,因此新的线程库就称为NPTL。NPTL比LinuxThreads效率高,且更符合POSIX规范,所以它已经成为glibc的一部分。本书所有线程相关的例程使用的线程库都是NPTL。
本章要讨论的线程相关的内容都属于POSIX线程(简称pthread)标准,而不局限于NPTL实现,具体包括:
- 创建线程和结束线程。
- 读取和设置线程属性
- POSIX线程同步方式:POSIX信号量、互斥锁和条件变量
最后我们将介绍在Linux环境下,库函数、进程、信号与多线程之间的相互影响。
创建线程和结束线程
Linux系统上,它们都定义在pthread.h头文件中。
pthread_create
创建一个线程的函数是pthread_create。其定义如下:
#include<pthread.h>
int pthread_create(pthread_t* thread , const pthread_attr_t* attr , void* (*start_routine)(void*) , void* arg);
thread参数是新线程的标识符,后续pthread_*函数通过它来引用新进程。其类型pthread_t的定义如下:
#include<bits/pthreadtypes.h> typedef unsigned long int pthread_t;
可见,pthread_t是一个整型类型。实际上,Linux上几乎所有的资源标识符都是一个整型数,比如socket、各种System V IPC标识符等。
attr参数用于设置新线程的属性。给他传递NULL表示使用默认线程属性。线程拥有众多属性,我们将在后面详细讨论。start_routine和arg参数分别指定新线程将运行的函数及其参数。
pthread_create成功时返回0,失败时返回错误码。一个用户可以打开的线程数量不能超过RLIMIT_NPROC软资源限制。此外,系统上所有用户创建的线程总数也不得超过/proc/sys/kernel/threads-max内核参数所定义的值。
pthread_exit
线程一旦被创建好,内核就可以调度内核线程序来执行start_routine函数指针所指向的函数了。线程在结束时最好调用如下函数以确保安全、干净地退出:
#include<pthread.h>
void pthread_exit(void* retval);
pthread_exit函数通过retval参数向线程的回收者传递其退出信息。他执行完之后不会返回到调用者,而且永远不会失败
pthread_join
一个进程中所有线程都可以调用pthread_join函数来回收其他线程(前提是目标线程是可回收的),即等待其他线程结束,这类似于回收进程的wait和waitpid系统调用。pthread_join的定义如下:
#include<pthread.h>
int pthread_join(pthread_t thread , void** retval);
thread参数是目标线程的标识符,retval参数是目标线程返回的退出信息。该函数会一直阻塞,直到被回收的线程结束为止。该函数成功时返回0,失败时则返回错误码。可能的错误如表。
错误码 描述 EDEADLK 可能引起死锁。比如两个线程互相针对对方调用pthread_join,或者线程对自身调用pthread_join EINVAL 目标线程是不可回收的,或者已经有其他线程在回收该目标线程 ESRCH 目标线程不存在
pthread_cancel
有时候我们希望异常终止一个线程,即取消线程,它是通过如下函数实现的:
#include<pthread.h>
int pthread_cancel(pthread_t thread);
thread参数是目标线程的标识符。该函数成功时返回0,失败则返回错误码。不过,接收到取消请求的目标线程可以决定是否允许被取消以及如何取消,这分别由如下两个函数完成:
#include<pthread.h>
int pthread_setcancelstate(int state , int *oldstate);
int pthread_setcanceltype(int type , int *oldtype);
这两个函数的第一个参数分别用于设置线程的取消状态(是否允许取消)和取消类型(如何取消),第二个参数则分别记录线程原来的状态和取消类型。state参数有两个可选值:
- PTHREAD_CANCEL_ENABLE允许线程被取消。它是线程被创建时的默认取消状态。
- PTHREAD_CANCEL_DISABLE,禁止线程被取消。这种情况下,如果一个线程收到取消请求,则它会将请求挂起,直到该线程允许被取消。
- PTHREAD_CANCEL_DEFERRED,允许目标线程推迟行动,直到它调用了下面几个所谓取消点函数中的一个:pthread_join、pthread_testcancel、pthread_cond_wait、pthread_cond_timedwait、sem_wait、sigwait。根据POSIX标准,其他可能阻塞的系统调用,比如read、wait,也可以成为取消点。不过为了安全起见,我们最好在可能会被取消的代码中调用pthread_testcancel函数以设置取消点。