线程是一种轻量级进程。与进程相比,线程给操作系统带来的创建、维护和管理负担要轻,因为与线程相关的信息非常少。它同时意味着线程的代价或开销比进程少。
线程可以在它的进程中创建另一个线程。一个进程中的所有线程称做同位体(peer)。所有的线程共享进程的资源和内存。线程不拥有任何资源。由任何线程创建的任何资源都被它的同位体共享。线程也可以在进程中挂起、恢复和终止其他线程。
进程与它的线程几乎共享一切,包括它的资源和环境变量。数据、文本片断以及所有资源都与进程相关,而不是与线程相关。线程是进程的租借者。线程发挥作用所需的一切都由进程提供和定义。所有的线程都有一个线程ID、定义线程状态的寄存器组、优先权以及堆栈。
1. 多线程处理
多线程进程拥有的线程多于一个,每个线程都执行进程的程序代码。如果进程只有一个线程,则称做主线程,它只有一个控制程序。进程的指令可以分解成子任务(典型情况是函数),这些子任务可以异步执行。线程可以执行一个函数或一组函数,每个线程的程序计数器将跟踪执行它的下一条指令,因此每个线程允许进程的函数独立执行,而与进程的主控制流程无关。
多线程应用程序指那些可以受益于子任务异步执行的应用程序。
一个有趣的NLP应用实例:
a. 验证字符串;b. 从字符串中提取噪音; c. 符号化字符串; d. 获取命令、文件名以及设备符号; e. 构建MCI命令并执行。
命令解析器由以下子任务组成:
a. 验证字符串; b.提取噪音; c. 符号化字符串; d. 执行。
符号化字符串子任务调用:
a. 获得命令符号; b. 获得设备名字符号; c. 获得文件名符号。
2. 线程与进程的相似之处
线程和进程都有ID、寄存器组、状态以及优先权。它们与之关联的信息块分别称做线程块(thread block)和进程信息块(process information block)。线程和子进程共享父进程的资源。进程打开的资源,线程和父进程的子进程可以立即访问,不需要额外的初始化或准备。创建后,线程和进程是与父或创建者独立的实体。父和子进程、线程一起竞争使用处理器。创建者对它创建的进程或线程可施加一些控制,同时也可以销毁、挂起、恢复和更改进程或线程的优先权。线程和进程可以改变自己的属性和创建新的资源,但它们不能访问其它进程或其之上线程的资源。
3. 线程与进程的不同之处
线程没有自己的地址空间。同一个进程内的多个线程在相同的地址空间内,可以容易地共享资源,它们可通过读取和写入数据到进程变量来通信。但父子进程则有着各自独立的地址空间,若要共享内存必须创建一个共享内存区域。父子进程必须使用进程间通信(IPC)机制在两者间通信和传递数据。
对其他进程的控制限制于父子进程之间进行,子进程无法对父进程施加控制。但进程内的线程则被视作同位体,处于相等的级别。不管是哪一个线程创建了哪一个线程,进程内的任何线程都可以销毁、挂起、恢复或更改其它线程的优先权。线程也要对整个进程施加控制。进程内的任何线程可以通过销毁主线程来销毁该进程。另外销毁主线程将导致该进程的所有线程销毁。对主线程的修改可能影响进程的所有线程。对进程优先权的更改将改变进程内继承了优先权但没有作修改的所有线程的优先权。
4. 线程的优点
多线程相对并发多进程而言需要较小的上下文切换开销。它还可以增加应用程序的吞吐能力。在单线程程序中一个I/O请求就可以停止整个进程,但通过多线程,当一个线程等待I/O请求时,应用程序可以继续执行。即一个线程阻塞等待I/O完成,其它的线程可以继续执行。线程并不需要子任务间的特殊通信机制,可以轻易地在任务间传递与接收数据,节省了系统资源的使用。如果使用多进程,则程序在启动、维持特殊通信机制时必须使用这些系统资源。线程通过使用进程内所有线程共享的内存来通信。
5. 线程的缺点
线程需要同步并发访问内存。进程是孤立的。如果任务分解为不同的进程,如果一个进程创建了不良数据,这一数据只限制于该进程。如果任务分解成多进程,一个线程可能产生影响其他线程的不良数据。相对于线程而言,进程更加独立。一个应用程序可以将任务分成多个进程。这些进程打包成模块,这些模块可以用于其它应用程序中。其他应用程序中的模块可以导入新的应用程序。线程依赖于它所属的进程,并且不能退出到创建它的进程之外。
6. 线程类型
关于如何使用线程来完成它们执行的子任务有如下三个策略:
a. 休眠(sleeper)和单步(one-shot); b. 先占工作(anticipating work); c. 延迟工作(deferring work)。
6.1 休眠和单步
休眠线程在系统中某事件发生前一直挂起。当事件发生时,得到恢复,并执行任务。此后,线程为非激活,直到下次事件再次发生。单步线程实践上就是休眠线程,但它们只执行任务一次后就终止。休眠与单步线程又称为监视线程,它们监视应用程序的某些方面,等待事件的发生,然后作为反应采取一些相应的行为。
6.2 先占工作
某个任务被请求前执行时就使用先占工作策略。对情况进行估计,并先占执行一些动作。它促进使用永不等待规则(never-wait rule)。它的目的是阻止非激活。这一规则表明预先执行一个可能不被使用的任务比处于空闲要好。
6.3 延迟工作
线程可以将任务延迟或推迟到将来某个时间执行的另一个线程。进行延迟的线程可能正执行一些重要的任务,不能执行另一项任务。此时线程可以创建另一个线程来执行任务,或者将任务交给一些已经存在的线程。可以在后台把一项延迟任务交给分离线程执行。
7. 线程相关信息
Win32环境中,线程具有一个模拟线程环境的信息结构,这个结构唤醒标志、线程的消息队列、虚拟化输入队列及其他变量。
POSIX环境有一个针对线程的属性对象。它可与一个线程关联,也可与多个线程关联。这些线程属性包括堆栈大小、优先权、规划策略、作用域、继承以及分离状态。也可能包含处理器信息。在多处理器系统中,可以将线程分配给特定的处理器或处理器范围。在对称或非对称多处理器系统中,系统提供一个返回特定线程处理器亲合掩码的函数。它是一个位数组,其中的每个位表示线程可以运行的处理器。
8. 线程创建
进程的主线程由操作系统自动创建。通过调用线程创建函数来创建后续或第二线程。我们可以通过调用线程库提供的函数也可直接调用OS提供的函数来创建线程。由线程库提供的函数在进行一些内务处理后最终调用OS函数。
线程在执行完毕后终止。当线程返回到创建它的函数时自动终止。线程的资源被释放。线程也可以显示调用一个线程终止函数。任何等待该线程终止的线程都将收到终止线程的ID或其信号。如果终止线程为主线程,它将导致整个进程的终止。
分离进程为异步子进程,它不继承父进程的任何属性,用作后台进程,不需要键盘输入或屏幕输出。分离进程在终止时不返回父进程,分离线程在这一点与分离进程相似。当线程终止时,终止线程的ID和状态由OS保存。如果进程不需要知道线程何时终止,就可以创建为分离线程。当线程终止时,线程ID和完成状态不会保存,也不会释放由线程使用的任何资源。为了创建一个分离线程,在创建的同时设置分离标志。运行的线程也可以为分离的。
守护程序(daemon)是分离进程的一个例子。守护进程在部分时间为阻塞状态,直到OS中发生了某事件,它才变成激活
Win32环境中,一个进程中的线程可以创建另一个进程中的线程。这称做远程线程。创建远程线程的过程与在同一进程中创建线程相似,除了需要一个目标进程的句柄之外。线程在目标进程的地址空间中执行。
9. 线程堆栈
每个线程都有自己的堆栈,它的大小在创建线程时被固定。线程堆栈在进程的地址空间的堆栈片断中分配。如果线程的创建都没有指定线程堆栈的大小,则由系统分配为缺省大小。它的大小与OS有关,取决于线程的最大数量、进程地址空间的分配大小以及OS资源所使用的空间。
10. 线程控制
进程内的线程被视为同位体,对资源有同样的访问权限、相互间施加同等的控制。它们对父线程的资源、环境变量以及看作全局的数据有相同的访问权限。线程能够访问其他线程打开或创建的资源。也可以销毁或创建其他线程包括主线程。
线程可以挂起进程内另一个线程的执行。挂起线程直到调用一个恢复它的函数时才会执行。进程内的任何线程都可以恢复挂起线程,除了挂起线程自己。线程可以在指定时间段内挂起自身。
11. 线程优先权
优先权规划用于决定哪一个线程使用处理器以及使用多长时间。在POSIX环境中,同一优先级的线程可以使用FIFO或循环规划或其他策略来规划。
对于其它线程所依赖的线程,应当更改它的优先权加速它的运行。线程优先权不应该为了让特定线程得到更多的处理器时间而更改。这将影响系统的整体操作性能。较高优先权的线程将主宰处理器,剥夺了其他线程有用的处理器时间,这就称做线程的饥饿。使用动态优先权机制的系统将对这种情况和系统中的其他 变化做出反应。
线程的优先权(priority)包括一个优先类(priority class)和一个优先级(priority level)。线程的优先类由它所属进程的优先类决定。新线程的优先级从创建它的线程继承。每个优先类都有一个级范围。
后台进程的优先权应当设的比前台进程低些。这样后台线程在没有前台线程准备执行时才使用处理器。所以当后台进程变成前台进程时,该进程的所有线程都将被推进(boost)或提升(raise)。这允许前台线程有能力对用户迅速作出反应。、
12. 线程状态
线程是处理器上的可规划单元(schedulable unit)。进程的每个线程都独立执行,同时竞争处理器时间。它们与同一进程中以及其他进程中的线程进行竞争。每个线程都有自己的上下 文和线 程状态。
为了阻塞进程,进程之上的所有线程都必须处于阻塞状态。
13. 线程与资源
在进程内,对于内存、处理器以及其他全局分配资源,线程都要与进程的其他 线程以及其他进程的线程来竞争。一些OS允许用户设置单个线程的竞争域。
14. 线程的实现模型:用户级线程
有三种线程实现(用户级线程;核心级线程;以上两种实现的混合途径)。
通过用户线实现,使用线程的程序链接到线程库。每个库函数调用都被翻译或映射属于进程一部分的运行时系统能够识别的调用。运行时系统实现线程管理。线程由库进行管理。这类线程包在OS的上层运行。对于OS这些线程不可见。这一类可执行可规划单元为-进程-线程(one thread per process)。不管库管理着多少线程,实际分配 处理器时间的线程只有一个,即主线程。用户线程具有多对一的线程关系。
核心级线程由OS来管理。进程有多个线程,对于核心都是可见的。通过核心线程,每创建一个线程,都存在一个创建核心线程的系统调用。这种一对一的关系使得它的开销比用户级线程大。不过用户级线程的一个缺点是无法使用多个处理器来使用系统。纯种管理只能发生于分配给该进程的单处理器上(想到了Python程序中多线程的实现正是如此)。通过kernel thread,则只要资源足够,可以给它自己的处理器分配线程。
结合user thread与kernel thread的优点可得到混合线程方式。在此模式中,线程被看作一个用户级线程,但被映射到核心级实体。只需创建少量核心线程。可能许多线程被请求,但只能创建当前激活的线程。创建一个线程池(thread pool)从一个线程到另一个线程进行模拟切换。线程池实际决定需要多少核心实体。POSIX.1c使用混合线程实现。