概述
线程是保证机器内部指令执行的连续性和持续性的各种属性的集合,所以线程包含了各种有关机器状态的信息,如当前执行的指令的位置,数据段的地址,数据寄存器信息等等。
进程 = N个线程+文件描述符+地址空间+其他数据信息 (N>=1)
这里的关键是线程的数量是多个,所以进程和线程的实体对应关系为一对多。这样带来的显著好处就是:由于多个线程可以共享同样的地址空间,同样的文件描述符,同样的指令空间等,所以在多处理器机器上,线程可以并行的执行。在单处理器机器上,线程可以并发执行。而对线程的切换和调度要比进程的开销小得多。
在计算机历史上,线程的引入是经历了从单道程序设计到多道程序设计,再到分时系统的诞生,然后是多进程操作系统的发明,最后诞生了多线程机制。线程本应该是最自然的编程方式,因为我们所接触到的世界本身就是并行的,异步的。任何计算都是对现实的一种模拟,所以并行的、异步的计算是对现实世界建模最基本的需求。
调度中心对高铁的调度,不同铁路上高速飞奔的火车,公路上遵守红绿灯交通信号的汽车,电视中欧洲杯现场激烈的足球赛,等待伴侣期间浏览社交网络的年轻人,无不都是以并行,异步的方式进行。我们每个人对这些自然方式都处理的很好,比如我们知道在购票的时候需要排队,看到红灯的时候需要停车,坐火车的时候可以玩手机,无聊的时候去睡觉。多线程编程就是提供一系列抽象的接口和设施,让你对这些习以为常的并行和异步的模式应用到计算机的应用程序中。比如排队可以用互斥量+队列数据结构来实现,红绿灯可以用信号量来实现,无聊的时候去睡觉可以用条件变量模拟等等。总之,就像3D图形学一样,多线程才是我们对世界建模的利器。
术语
异步:异步是指多个活动可以独立的或者并发的进行,除了遇到了一些强制性的依赖。我们可以说每个人的生活轨迹都是异步的,但是在某些时刻,他们可能会有一些依赖关系。
并发:并发是指那些看起来是并行执行事实上是串行执行的活动。POSIX中对并发执行的定义要求“一个函数在被某个执行线程调用时导致这个执行线程会挂起,但不应该导致其他的执行线程被永远的挂起”。并发并非并行,并发利用异步来提高响应速度和计算速度。一般来说,并发是指设备级(CPU、内存、I/O)的同时动作,并行是指处理器级(例如8核处理器)的同时动作。并发的进程是可能也是可以任意的交错执行的。
单处理器:只有一个程序员可见的执行单元,无论是增加了数学协处理器还是I/O协处理器,仍然认为是单处理器。
多处理器:共享指令并访问相同的物理内存的多个执行单元。
并行:同步进行的并发事件。并发即可存在于多处理器,也可存在于单处理器。并行仅仅存在于多处理器。
线程安全:是指代码可以被多个线程同时调用而不会产生错误的(破坏性的)结果。使一个函数成为线程安全的函数的最简单的方法就是在这个函数的入口处加锁,出口处解锁。这样会因为锁的粒度过大降低了系统的效率,更好的办法是找到函数内部的关键区,只对关键区加锁和解锁。最好的办法就是将关键区转换为关键数据,这样可以最大力度的降低锁的粒度。putchar函数就是一个典型的例子。
可重入:可重入是指“高效的线程安全”。可重入的函数内部没有必须串行执行的关键区域或者关键数据。可重入函数的实现要避免对静态类型的数据或者是一切与多线程同步相关的形式的依赖,即:函数内部不会出现同步。通常,实现可重入函数的方法是将函数的执行状态保存在一个由调用者负责维护的“上下文结构(context structure)”中,由调用者负责维护数据之间的同步。例如:readdir_r函数。
执行上下文:并发实体的状态。在没有出现线程的UNIX系统中为进程,在Pthreads系统中为线程。并发系统要提供能够独立地创建、删除、维护这些执行上下文的接口函数。
调度:用于决定在给定的时间点上,哪个或者哪些上下文会被执行。同时,还要负责上下文的切换。
同步:用于协调多个执行上下文对共享收据使用的机制。
部分实现
在Pthreads中,执行上下文是线程;同步的实现需要依赖于互斥量,条件变量,信号量。调度依赖于线程的执行优先级和操作系统的调度策略。
在没有thread模型的UNIX系统中,执行上下文为进程。不同的实现需要依赖于UNIX管道、套接字,POSXI消息队列或者其他进程通信协议,而任何通信协议都暗含着同步机制。