几乎所有现代操作系统都允许一个进程包含多个线程。
每个线程是CPU使用的一个基本单元。它包括线程ID,程序计数器,寄存器组和堆栈。
与同一进程其他线程共享代码段,数据段和其他操作系统资源。
*在同一进程的多线程之间,哪些程序状态部分会被共享?
堆内存,全局变量等。
线程独有的寄存器值与寄存内存等
任何形式的顺序程序
还有shell类程序,因为它必须密切检测其本身的工作空间。
*在处理器系统上采用多个用户级线程比在单处理器系统的单线程,提供更好的性能吗?
不能。
一个包括多用户线程的多线程系统无法在多处理器系统上同时使用不同的处理器。操作系统只能看到单一的进程且不会调度在不通过处理器上的不同进程的线程。
因此,多处理器系统执行多个用户线程没有性能优势。
多核(multicore)和多处理器(multiprocess)系统:无论多个计算核是在多个CPU芯片上还是在单个CPU芯片上,都成为多核或多处理器系统。
多核系统编程有五个方面的挑战:
通常,有两种类型的并行,数据并行(data parallelism)与任务并行(task parallelism)
有两种不同方法提供线程支持,用户层的用户线程(user thread)和内核层的内核线程(kernel threrad)
用户线程位于内核之上,管理无需内核支持,而内核线程由操作系统直接支持与管理。
用户线程与内核线程存在对应关系,如多对一模型,一对一模型与多对多模型。
多对一模型映射多个用户线程到一个内核线程。
如果一个线程执行阻塞系统调用,那么整个进程都会被阻塞。又因为任一时间只有一个线程可以访问内核,所以多个进程不能并行运行到多处理器系统上。
一对一模型映射每个用户线程到一个内核线程。
该模型在一个线程执行阻塞系统调用时,能允许另一个线程继续执行,所以它提供了更好的并发功能,
它也允许多个线程并行运行在多处理器系统上。
唯一的缺点是,创建一个用户线程就要创建一个相应的内核线程,这会增加开销,影响应用程序的性能。
多对多模型,多路复用多个用户级线程到同样数量或更少数量的内核线程。
内核线程的数量可能与特定应用程序或特定机器有关
开发人员可以创建任意多的用户线程,并且相应内核线程能在多处理器系统上并发执行。而且,当一个线程执行阻塞系统调用,内核可以调度另一个进程来执行。
双层模型
多对多模型的一种变种仍然多路复用多个用户级线程到同样数量或更少数量的内核线程,但也允许绑定某个用户线程到一个内核线程。
线程库(thread library)为程序员提供创建和管理线程的API。
实现线程库的主要方法有两种:
目前使用的三种主要线程库是:POSIX Pthreads, Windows, Java.
随着多核处理的日益增多,出现了拥有数百甚至数千线程的应用程序。设计这样的应用程序不起一个简单的事情,所以,为了解决这些困难并且更好支持设计多线程程序,出现一种隐式策略(implicit threading):
有一种方法是将多线程的创建与管理交给编译器和运行时库来完成。
先举一个例子,假设服务器每收到一个请求就会创建一个单独线程来处理请求。这样就会有潜在的问题。
第一个问题就是创建线程也是需要时间,第二个问题更为严重,如果允许所有并发请求都通过新进程来处理,那么我们没有限制系统内的并发执行线程的数量。无限制的线程可能耗尽系统资源,如CPU时间和内存。
这时候就可以使用线程池(thread tool)。
线程池的主要思想是:在进程开始时创建一定数量的线程,并加到池中以等待工作。当服务器收到请求时,它会唤醒池内的一个线程(如果有可用线程),并将需要服务的请求传递给它。一旦线程完成了服务,就会回到池中再等待工作。如果池内没有可用线程,那么服务器就会等待,知道有空线程为止。
线程池有以下三个优点:
这对那些不能支持大量并发线程的系统非常重要
OpenMP为一组编译指令和API,用于编写C,C++,Fortran等语言的程序。它支持共享内存环境下的并行编程。
OpenMP识别并行区域(parallel region),即可并行运行的代码块。
大中央调度(Grand Central Dispatch ,GCD),是Apple Mac OS X和iOS操作系统的一种技术。
它允许应用程序开发人员将某些代码区段并行运行。
本节讨论多线程出现的一些问题
UNIX信号(signal)用于通知进程某个特定事件已经发生。
信号的接收可以是同步的,也可以是异步的。
不管怎样,所有信号,都遵循相同的模式:
每个信号都有一个缺省信号处理程序(default signal handler),在处理信号时,由内核来运行。
这种缺省动作可以通过**用户定义处理程序(user-defined signal handler)**来改写。
如果一个进程有多个线程,那么信号应被传递到哪里去呢?一般有以下四种选择:
线程撤销(thread cancellation) 是在线程完成之前终止线程。
如用户按下网页浏览器的按钮,以停止进一步加载网页。加载网页可能需要多个线程,每个图像都是在一个单独线程中被加载的。当用户按下浏览器的停止按钮时,所有加载网页的线程被撤销。
需要撤销的线程,称为目标线程(target thread)。目标线程的撤销有两种情况:
异步撤销:一个线程立即终止目标线程
延迟撤销:目标线程不断检查它是否应终止,这允许目标线程有机会有序终止自己。
同一进程的线程共用进程的内存与数据,但线程也会有自己独有的数据,称这种数据为线程本地存储(Thread-Local Storage, TLS)
内核与线程库间可能需要通信,如多对多模型与双层模型。这种协调允许动态调整内核线程的数量,以便确保最优性能。
轻量级进程(LightWeight Process, LWP):许多系统在实现多对多或双层模型时,在用户和内核线程之间增加一个中间数据结构。这种数据结构就是轻量级进程。
对于用户级线程库,LWP表现为虚拟处理器,以便应用程序调度并运行用户线程。
每个LWP与一个内核线程相连,而只有内核线程才能通过操作系统调度以便运行于物理处理器。如果内核线程阻塞,LWP也会阻塞,上面的用户线程也会阻塞。
为了运行高效,应用程序可能需要一定数量的LWP。通常,每个并发的,阻塞的系统调用需要一个LWP。
假设一个应用程序为CPU密集型,并且运行在单个处理器上,在这种情况下,同一时间只有一个线程可以运行,所以只需要一个LWP就够了。
但是,一个IO密集型的应用程序可能需要多个LWP来执行。假设有5个不同的文件读请求可能同时发生,就需要5个LWP。因为每个都需要等待内核I/O的完成。如果只有4个LWP,那么第五个请求就必须等待一个LWP从内核中返回。
调度器激活(scheduler activation):用户线程库与内核之间的一种通信方案。
工作流程如下:
这个步骤叫做回调(upcall),它有线程库通过**回调处理程序(upcall handler)**来处理。
该事件的回调处理程序也需要一个虚拟处理器,内核可能分配一个新的虚拟处理器,或抢占一个用户线程并在其虚拟处理器上运行回调处理程序。
多线程编程的两种并行类型
多线程四种模型与特点
线程库的实现
线程池的作用与优点
多线程之间的信号处理
线程撤销的概念
调度程序激活的流程