1线程基础
multithreading可以被翻译成多线程控制。与传统的UNIX不同,一个传统 的UNIX进程包含一个单线程,而多线程(MT)则把一个进程分成很多可执行线 程,每一个线程都独立运行。
阅读本章可以让你理解:
Defining Multithreading Terms
Benefiting From Multithreading
Looking At Multithreading Structure
Meeting Multithreading Standards
因为多线程可以独立运行,用多线程编程可以
1) 提高应用程序响应;
2) 使多CPU系统更加有效;
3) 改善程序结构;
4) 占用更少的系统资源;
5) 改善性能;
1.1定义多线程术语:
线程:在进程的内部执行的指令序列;
单线程:单线程;
多线程:多线程;
用户级线程:在用户空间内的由线程函数库进程控制的现成;
轻进程:又称LWP,内核内部的执行核代码和系统调用的线程;
绑定(bound)线程:永远限制在LWP内的线程;
非绑定(unbound)线程:在LWP动态捆绑和卸绑的线程;
记数信号量:一个基于内存的同步机制;
1.1.1定义同时(concurrency)和并行(parallism):
在进程内至少同时有两个线程进行(process)时存在同时性问题;至少同时有两个线程在执行时存在并行问题;
在单处理器上执行的多线程的进程内部,处理器可以在线程中间切换执行, 这样实现了同时执行;在共享内存多处理器上执行的同一个多线程进程,每一 个线程可以分别在不同的处理器上进行,是为并行。
当进程里的线程数不多于处理器的数量时,线程支持系统和操作系统保 证线程在不同的处理器上执行。例如在一个m处理器和m线程运行一个矩阵乘法, 每一个线程计算一列。
1.2多线程的益处
1.2.1提高应用程序响应
任何一个包含很多互不关联的操作(activity)的程序都可以被重新设计, 使得每一个操作成为一个线程。例如,在一个GUI(图形用户界面)内执行一 个操作的同时启动另外一个,就可以用多线程改善性能。
1.2.2使多处理器效率更高
典型情况下,有同时性需求的多线程应用程序不需要考虑处理器的数量。应用程序的性能在被多处理器改善的同时对用户是透明的。
数学计算和有高度并发性需求的应用程序,比如矩阵乘法,在多处理器平台上可以用多线程来提高速度。
1.2.3改善程序结构
许多应用程序可以从一个单一的、巨大的线程改造成一些独立或半独立的 执行部分,从而得到更有效的运行。多线程程序比单线程程序更能适应用户需 求的变更。
1.2.4占用较少的系统资源
应用程序可以通过使用两个或更多的进程共享内存的办法来实现多于一个 现成的控制。然而,每一个进程都要有一个完整的地址空间和操作系统状态表 项。用于创建和维护多进程大量的状态表的开销与多线程方法相比,在时间上 和空间上都更为昂贵。而且,进程所固有的独立性使得程序员花费很多精力来 实现进程间的通信和同步。
1.2.5把线程和RPC结合起来
把多线程和RPC(remote procedure call,远程过程调用)结合起来,你可以使用没内存共享的多处理器(比方说一个工作站组)。这种结构把这组工作站当作一个大的多处理器系统,使应用程序分布得更加容易。
例如,一个线程可以创建子线程,每一个子进程可以做RPC,调用另外一 台机器上的过程。尽管最早的线程仅仅创建一些并行的线程,这种并行可以包 括多台机器的运行。
1.2.6提高性能
本部分的性能数据是从SPARC station2(Sun 4/75)上采集的。测量精度为微秒。
1. 线程创建时间
表1-1显示了使用thread package做缓存的缺省堆栈来创建线程的时 间。时间的测量仅仅包括实际的生成时间。不包括切换到线程的时间。比 率(ratio)列给出了该行生成时间与前一行的比。 数据表明,线程是更加经济的。创建一个新进程大概是创建一个 unbound线程的30倍,是创建一个包含线程和LWP的bound线程的5倍。
Table 1-1 Thread Creation Times
Operation Microseconds Ritio
Create unbound thread 52 -
Create bound thread 350 6.7
Fork() 1700 32.7
2. 线程同步(synchronization)时间
表1-2列出了两个线程使用pv操作的同步时间。
Table 1-2 Thread Synchronization Times
Operation Microseconds Ratio
Unbound thread 66 -
Bound thread 390 5.9
Between Processes 200 3
1.3多线程结构一览
传统的UNIX支持现成概念--每一个进程包含一个单线程,所以用多进程就 是使用多线程。但是一个进程还有一个地址空间,创建一个新进程意味着需要 创建一个新的地址空间。
因此,创建一个进程是昂贵的,而在一个已经存在的进程内部创建线程是 廉价的。创建一个线程的时间比创建一个进程的时间要少成千倍,部分是因为 在线程间切换不涉及地址空间的切换。
在进程内部的线程间通信很简单,因为线程们共享所有的东西--特别是地址空间。所以被一个线程生成的数据可以立刻提供给其他线程。
支持多线程的接口(界面)是通过一个函数库libthread实现的。多线程通过把内核级资源和用户级资源独立开来提供了更多的灵活性。
1.3.1用户级线程
线程仅仅在进程内部是可见的,在进程内部它们共享诸如地址空间、已经 打开的文件等所有资源。以下的状态是线程私有的,即每一个线程的下列状态 在进程内部是唯一的。
.线程号(Thread ID)
.寄存器状态(包括程序计数器和堆栈指针)
.堆栈
.信号掩码(Signal mask)
.优先级(Priority)
.线程私有的存储段(Thread-private storage)
因为线程共享进程的执行代码和大部分数据,共享数据被一个线程修改之 后可以进程内的其他线程见到。当一个进程内部线程与其他线程通信的时候, 可以不经过操作系统。
线程是多线程编程的主要主要借口。用户级的线程可以在用户空间操作, 从而避免了与内核之间的互相切换。一个应用程序可以拥有几千个线程而不占 用太多的内核资源。占用内核资源的多少主要决定于应用程序本身。
在缺省情况下,线程是非常轻便的。但是,为了控制一个线程(例如,更 多地控制进程调度策略),应用程序应当绑定线程。当一个应用程序把线程的所 有执行资源绑定后,线程就变成了内核资源(参见第9页"bound 线程")。 总之,solaris用户级线程是:
.创建的低开销,因为只在运行是占用用户地址空间的虚拟内存的几个bit。
.快速同步,因为同步是在用户级进行,不需要接触到内核级。
.可以通过线程库libthread很容易地实现。
图1-1 多线程系统结构(略)
1.3.2轻进程(Lightweight Porcesses:LWP)
线程库采用内核支持的称为轻进程的底层控制线程。你可以把LWP看作一个 可以执行代码和系统调用的虚拟的CPU。
大多数程序员使用线程是并不意识到LWP的存在。下面的内容仅仅帮助理解 bound和unbound线程之间的区别。
------------------------------------
NOTE:Solaris2.x的LWP不同于SunOs4.0的LWP库,后者在solaris2.x中不再被支持。
------------------------------------
类似于在stdio中fopen和fread调用open和read,线程接口调用LWP接口, 原因是一样的。
LWP建立了从用户级到内核级的桥梁。每个进程包含了一个或更多LWP,每个 LWP运行着一个或多个用户线程。创建一个现成通常只是建立一个用户环境 (context),而不是创建一个LWP。
在程序员和操作系统的精心设计下,用户级线程库保证了可用的LWP足够驱动 当前活动的用户级线程。但是,用户线程和LWP之间不是一一对应的关系,用户级 线程可以在LWP之间自由切换。
程序员告诉线程库有多少线程可以同时"运行"。例如,如果程序员指定最多有 三个线程可以同时运行,至少要有3个可用的LWP。如果有三个可用的处理器,线程 将并行进行。如果这里只有一个处理器,操作系统将在一个处理器上运行三个LWP。 如果所有的LWP阻塞,线程库将在缓冲池内增加一个LWP。
当一个用户线程由于同步原因而阻塞,它的LWP将移交给下一个可运行的线程。 这种移交是通过过程间的连接(coroutine linkage),而不是做系统调用而完成。
操作系统决定哪一个LWP什么时候在哪一个处理器上运行。它不考虑进程中线 程的类型和数量。内核按照LWP的类型和优先级来分配CPU资源。线程库按照相同 的方法来为线程分配LWP。每个LWP被内核独立地分发,执行独立的系统调用,引 起独立的页错误,而且在多处理器的情况下将并行执行。
一些特殊类型的LWP可以不被直接交给线程。(!?不明)
1.3.3非绑定线程Unbound Threads
在LWP缓冲池中排队的线程称为unbound thread。通常情况下我们的线程都是 unbound的,这样他们可以在LWP之间自由切换。
线程库在需要的时候激活LWP并把它们交给可以执行的线程。LWP管理线程的 状态,执行线程的指令。如果线程在同步机制中被阻塞,或者其他线程需要运行, 线程状态被存在进程内存中,LWP被移交给其他线程。
1.3.4绑定线程Bound Threads
如果需要,你可以将一个线程绑定在某个LWP上。
例如,你可以通过绑定一个线程来实现:
1. 将线程全局调度(例如实时)
2. 使线程拥有可变的信号栈
3. 给线程分配独立的定时器和信号(alarm)
在线程数多于LWP时,bounded比unbound线程体现出一些优越性。
例如,一个并行的矩阵计算程序在每个线程当中计算每一行。如果每个处理器 都有一个LWP,但每个LWP都要处理多线程,每个处理器将要花费相当的时间来切换 线程。在这种情况下,最好使每个LWP处理一个线程,减少线程数,从而减少线程 切换。
在一些应用程序中,混合使用bound和unbound线程更加合适。
例如,有一个实时的应用程序,希望某些线程拥有全局性的优先级,并被实时 调度,其他线程则转入后台计算。另一个例子是窗口系统,大多数操作都是 unbound的,但鼠标操作需要占用一个高优先级的,bound的,实时的线程。
1.4多线程的标准
多线程编程的历史可以回溯到二十世纪60年代。在UNIX操作系统中的发展是从 80年代中期开始的。也许是令人吃惊的,关于支持多线程有很好的协议,但是今 天我们仍然可以看到不同的多线程开发包,他们拥有不同的接口。
但是,某几年里一个叫做POSIX1003.4a的小组研究多线程编程标准。当标准完 成后,大多数支持多线程的系统都支持POSIX接口。很好的改善了多线程编程的可 移植性。
solaris多线程支持和POSIX1003.4a没有什么根本性的区别。虽然接口是不同 的,但每个系统都可以容易地实现另外一个系统可以实现的任何功能。它们之间没 有兼容性问题,至少solaris支持两种接口。即使是在同一个应用程序里,你也可 以混合使用它们。
用solaris线程的另一个原因是使用支持它的工具包,例如多线程调试工具 (multighreaded debugger)和truss(可以跟踪一个程序的系统调用和信号), 可以很好地报告线程的状态。
multithreading可以被翻译成多线程控制。与传统的UNIX不同,一个传统 的UNIX进程包含一个单线程,而多线程(MT)则把一个进程分成很多可执行线 程,每一个线程都独立运行。
阅读本章可以让你理解:
Defining Multithreading Terms
Benefiting From Multithreading
Looking At Multithreading Structure
Meeting Multithreading Standards
因为多线程可以独立运行,用多线程编程可以
1) 提高应用程序响应;
2) 使多CPU系统更加有效;
3) 改善程序结构;
4) 占用更少的系统资源;
5) 改善性能;
1.1定义多线程术语:
线程:在进程的内部执行的指令序列;
单线程:单线程;
多线程:多线程;
用户级线程:在用户空间内的由线程函数库进程控制的现成;
轻进程:又称LWP,内核内部的执行核代码和系统调用的线程;
绑定(bound)线程:永远限制在LWP内的线程;
非绑定(unbound)线程:在LWP动态捆绑和卸绑的线程;
记数信号量:一个基于内存的同步机制;
1.1.1定义同时(concurrency)和并行(parallism):
在进程内至少同时有两个线程进行(process)时存在同时性问题;至少同时有两个线程在执行时存在并行问题;
在单处理器上执行的多线程的进程内部,处理器可以在线程中间切换执行, 这样实现了同时执行;在共享内存多处理器上执行的同一个多线程进程,每一 个线程可以分别在不同的处理器上进行,是为并行。
当进程里的线程数不多于处理器的数量时,线程支持系统和操作系统保 证线程在不同的处理器上执行。例如在一个m处理器和m线程运行一个矩阵乘法, 每一个线程计算一列。
1.2多线程的益处
1.2.1提高应用程序响应
任何一个包含很多互不关联的操作(activity)的程序都可以被重新设计, 使得每一个操作成为一个线程。例如,在一个GUI(图形用户界面)内执行一 个操作的同时启动另外一个,就可以用多线程改善性能。
1.2.2使多处理器效率更高
典型情况下,有同时性需求的多线程应用程序不需要考虑处理器的数量。应用程序的性能在被多处理器改善的同时对用户是透明的。
数学计算和有高度并发性需求的应用程序,比如矩阵乘法,在多处理器平台上可以用多线程来提高速度。
1.2.3改善程序结构
许多应用程序可以从一个单一的、巨大的线程改造成一些独立或半独立的 执行部分,从而得到更有效的运行。多线程程序比单线程程序更能适应用户需 求的变更。
1.2.4占用较少的系统资源
应用程序可以通过使用两个或更多的进程共享内存的办法来实现多于一个 现成的控制。然而,每一个进程都要有一个完整的地址空间和操作系统状态表 项。用于创建和维护多进程大量的状态表的开销与多线程方法相比,在时间上 和空间上都更为昂贵。而且,进程所固有的独立性使得程序员花费很多精力来 实现进程间的通信和同步。
1.2.5把线程和RPC结合起来
把多线程和RPC(remote procedure call,远程过程调用)结合起来,你可以使用没内存共享的多处理器(比方说一个工作站组)。这种结构把这组工作站当作一个大的多处理器系统,使应用程序分布得更加容易。
例如,一个线程可以创建子线程,每一个子进程可以做RPC,调用另外一 台机器上的过程。尽管最早的线程仅仅创建一些并行的线程,这种并行可以包 括多台机器的运行。
1.2.6提高性能
本部分的性能数据是从SPARC station2(Sun 4/75)上采集的。测量精度为微秒。
1. 线程创建时间
表1-1显示了使用thread package做缓存的缺省堆栈来创建线程的时 间。时间的测量仅仅包括实际的生成时间。不包括切换到线程的时间。比 率(ratio)列给出了该行生成时间与前一行的比。 数据表明,线程是更加经济的。创建一个新进程大概是创建一个 unbound线程的30倍,是创建一个包含线程和LWP的bound线程的5倍。
Table 1-1 Thread Creation Times
Operation Microseconds Ritio
Create unbound thread 52 -
Create bound thread 350 6.7
Fork() 1700 32.7
2. 线程同步(synchronization)时间
表1-2列出了两个线程使用pv操作的同步时间。
Table 1-2 Thread Synchronization Times
Operation Microseconds Ratio
Unbound thread 66 -
Bound thread 390 5.9
Between Processes 200 3
1.3多线程结构一览
传统的UNIX支持现成概念--每一个进程包含一个单线程,所以用多进程就 是使用多线程。但是一个进程还有一个地址空间,创建一个新进程意味着需要 创建一个新的地址空间。
因此,创建一个进程是昂贵的,而在一个已经存在的进程内部创建线程是 廉价的。创建一个线程的时间比创建一个进程的时间要少成千倍,部分是因为 在线程间切换不涉及地址空间的切换。
在进程内部的线程间通信很简单,因为线程们共享所有的东西--特别是地址空间。所以被一个线程生成的数据可以立刻提供给其他线程。
支持多线程的接口(界面)是通过一个函数库libthread实现的。多线程通过把内核级资源和用户级资源独立开来提供了更多的灵活性。
1.3.1用户级线程
线程仅仅在进程内部是可见的,在进程内部它们共享诸如地址空间、已经 打开的文件等所有资源。以下的状态是线程私有的,即每一个线程的下列状态 在进程内部是唯一的。
.线程号(Thread ID)
.寄存器状态(包括程序计数器和堆栈指针)
.堆栈
.信号掩码(Signal mask)
.优先级(Priority)
.线程私有的存储段(Thread-private storage)
因为线程共享进程的执行代码和大部分数据,共享数据被一个线程修改之 后可以进程内的其他线程见到。当一个进程内部线程与其他线程通信的时候, 可以不经过操作系统。
线程是多线程编程的主要主要借口。用户级的线程可以在用户空间操作, 从而避免了与内核之间的互相切换。一个应用程序可以拥有几千个线程而不占 用太多的内核资源。占用内核资源的多少主要决定于应用程序本身。
在缺省情况下,线程是非常轻便的。但是,为了控制一个线程(例如,更 多地控制进程调度策略),应用程序应当绑定线程。当一个应用程序把线程的所 有执行资源绑定后,线程就变成了内核资源(参见第9页"bound 线程")。 总之,solaris用户级线程是:
.创建的低开销,因为只在运行是占用用户地址空间的虚拟内存的几个bit。
.快速同步,因为同步是在用户级进行,不需要接触到内核级。
.可以通过线程库libthread很容易地实现。
图1-1 多线程系统结构(略)
1.3.2轻进程(Lightweight Porcesses:LWP)
线程库采用内核支持的称为轻进程的底层控制线程。你可以把LWP看作一个 可以执行代码和系统调用的虚拟的CPU。
大多数程序员使用线程是并不意识到LWP的存在。下面的内容仅仅帮助理解 bound和unbound线程之间的区别。
------------------------------------
NOTE:Solaris2.x的LWP不同于SunOs4.0的LWP库,后者在solaris2.x中不再被支持。
------------------------------------
类似于在stdio中fopen和fread调用open和read,线程接口调用LWP接口, 原因是一样的。
LWP建立了从用户级到内核级的桥梁。每个进程包含了一个或更多LWP,每个 LWP运行着一个或多个用户线程。创建一个现成通常只是建立一个用户环境 (context),而不是创建一个LWP。
在程序员和操作系统的精心设计下,用户级线程库保证了可用的LWP足够驱动 当前活动的用户级线程。但是,用户线程和LWP之间不是一一对应的关系,用户级 线程可以在LWP之间自由切换。
程序员告诉线程库有多少线程可以同时"运行"。例如,如果程序员指定最多有 三个线程可以同时运行,至少要有3个可用的LWP。如果有三个可用的处理器,线程 将并行进行。如果这里只有一个处理器,操作系统将在一个处理器上运行三个LWP。 如果所有的LWP阻塞,线程库将在缓冲池内增加一个LWP。
当一个用户线程由于同步原因而阻塞,它的LWP将移交给下一个可运行的线程。 这种移交是通过过程间的连接(coroutine linkage),而不是做系统调用而完成。
操作系统决定哪一个LWP什么时候在哪一个处理器上运行。它不考虑进程中线 程的类型和数量。内核按照LWP的类型和优先级来分配CPU资源。线程库按照相同 的方法来为线程分配LWP。每个LWP被内核独立地分发,执行独立的系统调用,引 起独立的页错误,而且在多处理器的情况下将并行执行。
一些特殊类型的LWP可以不被直接交给线程。(!?不明)
1.3.3非绑定线程Unbound Threads
在LWP缓冲池中排队的线程称为unbound thread。通常情况下我们的线程都是 unbound的,这样他们可以在LWP之间自由切换。
线程库在需要的时候激活LWP并把它们交给可以执行的线程。LWP管理线程的 状态,执行线程的指令。如果线程在同步机制中被阻塞,或者其他线程需要运行, 线程状态被存在进程内存中,LWP被移交给其他线程。
1.3.4绑定线程Bound Threads
如果需要,你可以将一个线程绑定在某个LWP上。
例如,你可以通过绑定一个线程来实现:
1. 将线程全局调度(例如实时)
2. 使线程拥有可变的信号栈
3. 给线程分配独立的定时器和信号(alarm)
在线程数多于LWP时,bounded比unbound线程体现出一些优越性。
例如,一个并行的矩阵计算程序在每个线程当中计算每一行。如果每个处理器 都有一个LWP,但每个LWP都要处理多线程,每个处理器将要花费相当的时间来切换 线程。在这种情况下,最好使每个LWP处理一个线程,减少线程数,从而减少线程 切换。
在一些应用程序中,混合使用bound和unbound线程更加合适。
例如,有一个实时的应用程序,希望某些线程拥有全局性的优先级,并被实时 调度,其他线程则转入后台计算。另一个例子是窗口系统,大多数操作都是 unbound的,但鼠标操作需要占用一个高优先级的,bound的,实时的线程。
1.4多线程的标准
多线程编程的历史可以回溯到二十世纪60年代。在UNIX操作系统中的发展是从 80年代中期开始的。也许是令人吃惊的,关于支持多线程有很好的协议,但是今 天我们仍然可以看到不同的多线程开发包,他们拥有不同的接口。
但是,某几年里一个叫做POSIX1003.4a的小组研究多线程编程标准。当标准完 成后,大多数支持多线程的系统都支持POSIX接口。很好的改善了多线程编程的可 移植性。
solaris多线程支持和POSIX1003.4a没有什么根本性的区别。虽然接口是不同 的,但每个系统都可以容易地实现另外一个系统可以实现的任何功能。它们之间没 有兼容性问题,至少solaris支持两种接口。即使是在同一个应用程序里,你也可 以混合使用它们。
用solaris线程的另一个原因是使用支持它的工具包,例如多线程调试工具 (multighreaded debugger)和truss(可以跟踪一个程序的系统调用和信号), 可以很好地报告线程的状态。