线程的动机是充分利用多核,方便地实现并行编程。然而,早期的硬件厂商纷纷实现自己的一套线程标准,这样,程序员无法开发可移植的软件。为了结束这个局面,对UNIX系统,IEEE制定了一个C语言线程编程接口标准(IEEE POSIX 1003.1c)。参考这个标准的实现就被称作POSIX threads 或者 Pthreads.
什么是线程?
在理解线程之前,可以考察一个UNIX进程。一个进程由操作系统创建,包含一些程序资源和运行状态的信息,包括:
- 进程ID, 进程组ID, 用户ID和组ID
- 运行时环境
- 运行时目录
- 程序指令序列
- 寄存器
- 栈
- 堆
- 文件句柄
- 信号量动作
- 共享库
- 程序间通信的一些技术和工具(例如消息队列,管道和共享内存)
线程是一些独立的指令流,并且可以被操作系统调度。指令流执行的控制之所以是独立的,并且能被调度,源于线程有独立的:
- 栈指针
- 寄存器
- 调度属性(例如:策略和优先级)
- 挂起和阻塞的信号量集合
- 线程相关的数据
总而言之,在UNIX中,一个线程:
- 存在于进程中,可以使用进程的资源
- 只要宿主进程存在,并且有操作系统的支持,一个线程就可以有独立的控制流程
- 和其他独立(或有依赖)的线程共享进程的资源
- 如果宿主进程退出,线程也会退出
- 线程是轻量级的是因为负载已经由宿主进程承担
因为多个线程存在于一个进程中,这会导致下面几种情况:
- 某个线程改变了进程中的某个资源(例如打开一个文件,或关闭一个文件)会被其他线程观察到
- 多线程可以拥有对同一个数据的指针
- 多个线程可以访问共享内存,所以,需要开发人员做显式的同步操作
为什么要引入Pthreads
Pthreads的一个主要目标是提升程序的性能。创建线程和线程的调度并不会占用操作系统很多的负载。进程中的所有线程共享相同的地址空间,所以,线程之间的通信相比较进程会更高效和容易。多线程应用可以通过下面的途径达到性能上的提升:
- CPU和I/O同时工作。例如在一个有CPU密集型任务的进程中,当在等待I/O结束的时候,另外的线程可以占用CPU,执行那些耗费CPU的任务。
- 优先级调度。重要的任务可抢占低优先级的执行。
- 异步的事件调度。例如:一个web服务器在传输上一个请求的数据的同时,还可以管理接收新的请求。
设计多线程的程序
在现代多核CPU的系统中,pthreads很优雅地适合并行编程。实际上,在进行并行编程的过程中,需要有诸多考虑:
- 使用哪种并行编程模型
- 问题划分
- 负载均衡
- 线程之间的通信
- 数据依赖
- 同步和条件竞争
- 内存操作
- I/O操作
- 程序复杂度
- 程序员的付出和时间
通常来讲,为了充分利用多线程带来的益处,一个程序员必须将程序划分为离散的,互相独立的任务,这些任务可以兵符执行。比如说,routine1和routine2的执行可以互相重叠,那么,就可以利用多线程。通常,拥有下面特点的程序就适合使用多线程:
- 多个任务可以并发执行某个工作,或者操作某项数据
- 可能会被很长的I/O操作阻塞
- 必须响应多个异步事件
- 某些工作比另外的更加重要(使用优先级中断)
有几个通用的多线程模式:
- 管理者/工作者:一个被称作管理者的独立线程将任务委派给多个工作线程。通常情况下,管理者线程会维护所有的输入。至少有两类通用的管理者/工作者模型:静态的工作者线程池和动态的工作者线程池。
- 管道:一个任务可以划分为多个子操作。每一个子操作被不同的线程执行,但是,整个任务还是序列化执行的。汽车装配流水线很形象地描述了这中模式。
- 同行:类似于管理者/工作者模型,不同的是,一个主线程创建了其他线程后,主线程也会参与任务的执行。
线程管理
创建和终止线程的接口有下面几种:
pthread_create (thread,attr,start_routine,arg) pthread_exit (status) pthread_cancel (thread) pthread_attr_init (attr) pthread_attr_destroy (attr)
一开始,main方法默认会创建一个独立的线程。其他的线程必须由程序员显式创建。可以在任何时候和任何地方创建一个新的线程。被创建的线程也可以创建新的线程。
Pthreads提供了多个接口被用于线程定义如何调度线程。例如,可以指定线程的调度使用FIFO(first in first out), RR(round-robin)或者OTHER(由操作系统来控制)的调度方案。Pthreads还提供了设置线程优先级的接口。
线程终止有多种途径:
- 线程正常结束自己的工作并返回。
- 线程调用了pthread_exit接口。
- 其他线程调用pthread_cancel取消该线程的运行。
- 宿主进程调用exec或者exit结束整个进程的运行。
- main方法结束。
pthread_exit并不会关闭打开的文件句柄,任何在线程中打开的文件都会一直保持打开状态,尽管线程已经中止。