线程概念
典型的UNIX进程可以看成只有一个控制线程:一个进程在某一时刻只能做一件事情。有了多个控制线程之后,在程序设计时就可以把进程设计成在某一时刻能够做不止一件事,每个线程处理各自独立的任务。这种方法有很多好处。
- 通过为每种事件类型分配单独的处理线程,可以简化处理异步事件的代码。每个线程在进行事件处理时可以采用同步编程模式,同步编程模式比异步编程模式简单。
- 多个进程必须使用操作系统提供的复杂机制才能实现内存和文件描述符的共享。多个线程自动地可以访问相同的存储地址空间和文件描述符。
- 有些问题可以分解从而提高整个程序的吞吐量。在只有一个控制线程的情况下,一个单线程进程要完成多个任务,只需要把这些任务串行化。但有多个控制线程时,相互独立的任务的处理就可以交叉进行,此时只需要为每个任务分配一个单独的线程。当然只有在两个任务的处理过程互不依赖的情况下,两个任务才可以交叉执行。
- 交互的程序同样通过使用多线程来改善相应事件,多线程可以把程序中处理用户输入输出的部分与其他部分分开。
每个线程都包含有表示执行环境所必需的信息,其中包括进程中标识线程的线程ID,一组寄存器值,栈,调度优先级和策略,信号屏蔽字,errno
变量以及线程私有数据。
一个进程的所有信息对该进程的所有线程都是共享的,包括可执行程序的代码,程序的全局内存和堆内存,栈以及文件描述符。
线程标识
线程ID只有在它所属的进程上下文中才有意义。
线程ID是用pthread_t
数据类型来表示的,实现的时候可以用一个结构来代表pthread_t
数据类型,所以可移植的操作系统实现不能把它作为整数处理。因此必须使用一个函数来对两个线程ID进行比较。
#include <pthread.h>
int pthread_equal(pthread_t tid1, pthread_t tid2);
//返回值:若相等,返回非0数值,否则,返回0
线程可以通过调用pthread_self
函数获得自身的线程ID。
#include <pthread.h>
pthread_t pthread_self(void);
//返回值:调用线程的线程ID
线程创建
在传统UNIX进程模型中,每个进程只有一个控制线程。从概念上讲,这与基于线程的模型中每个进程知包含一个线程是相同的。在POSIX线程的情况下,程序开始运行时,它也是以单进程中的单个控制线程启动的。
新增的线程可以通过调用pthread_create
函数创建。
#include <pthread.h>
int pthread_create(pthread_t* restrict tidp,
const pthread_attr_t* restrict attr,
void *(*start_rtn)(void *), void* restrict arg);
//返回值:若成功,返回0,否则,返回错误编号
当pthread_create
成功返回时,新创建线程的线程ID会被设置成tidp
所指的内存单元。attr
参数用于定制各种不同的线程属性。
新创建的线程从start_rtn
函数的地址开始运行,该函数只有一个无类型指针参数arg
。如果需要向start_rtn
函数传递的参数有一个以上,那么需要把这些参数放到一个结构中,然后把整个结构的地址作为arg
参数传入。
线程创建时不能保证哪个线程会先运行。
新创建的线程可以访问进程的地址空间,并且继承调用线程的浮点环境和信号屏蔽字,但是该线程的挂起信号集会被清除。
注意,pthread
函数在调用失败时通常会返回错误码,它们并不像其他的POSIX函数一样设置errno
。每个线程都提供errno
的副本。
线程终止
如果进程中的任意线程调用exit
,_Exit
或者_exit
,那么整个进程就会终止。与此类似,如果默认的动作是终止进程,那么,发送到线程的信号就会终止整个进程。
单个线程可以通过3种方式退出,因此可以在不终止整个进程的情况下,停止它的控制流。
- 线程可以简单地从启动例程中返回,返回值是线程的退出码。
- 线程可以被同一进程中的其他线程取消。
- 线程调用
pthread_exit
。
#include <pthread.h>
void pthread_exit(void* rval_ptr);
rval_ptr
参数是一个无类型指针,与传给启动例程的单个参数类似。进程中的其他线程也可以通过调用pthread_join
访问到这个指针。
#include <pthread.h>
int pthread_join(pthread_t thread, void** rval_ptr);
//返回值:若成功,返回0,否则,返回错误编号
调用线程将一直阻塞,直到指定的线程调用pthread_exit
或从启动例程中返回或者被取消。如果线程简单地从它的启动例程中返回,rval_ptr
就包含返回码。如果线程被取消,由rval_ptr
指定的内存单元就设置为PTHREAD_CANCELED
。
可以通过调用pthread_join
自动把线程置于分离状态,这样资源可以恢复。如果线程已经处于分离状态,pthread_join
调用就会失败,返回EINVAL。
如果对线程的返回值并不感兴趣,那么可以把rval_ptr
设置为NULL。
线程可以通过调用pthread_cancel
函数来请求取消同一进程中的其他线程。
#include <pthread.h>
int pthread_cancel(pthread_t tid);