# 综述
# 线程的引入
- 进程具有二个基本属性:
是一个拥有资源的独立单位:它可独立分配虚地址空间、主存和其它
又是一个可独立调度和分派的基本单位
这二个基本属性使进程成为并发执行的基本单位
在一些早期的 OS 中,比如大多数 UNIX 系统、Linux 等,进程同时具有这二个属性。 - 由于进程是一个资源的拥有者,因而在进程创建、撤销、调度切换时,系统需要付出较大的时空开销。
- 进程的数目不宜过多,进程切换频率不宜过高,限制了并发程度
- 操作系统的设计目标
- 提高并发度
- 减小系统开销
- 将进程的两个基本属性分开,对于拥有资源的基本单位,不对其进行频繁切换,对于调度的基本单位,不作为拥有资源的单位,“轻装上阵”
- 引入线程的目的是简化线程间的通信,以小的开销来提高进程内的并发程度。
# 线程概念
# 进程与线程
- 进程:资源分配单位(存储器、文件)和 CPU 调度(分派)单位。又称为 "任务 (task)"
- 线程:作为 CPU 调度单位,而进程只作为其他资源分配单位。
- 只拥有必不可少的资源,如:线程状态、程序计数器、寄存器上下文和栈
- 同样具有就绪、阻塞和执行三种基本状态
- 与同属一个进程的其它线程共享进程拥有的全部资源
- 可并发执行
# 线程的优点
-
线程的优点:减小并发执行的时间和空间开销(线程的创建、退出和调度),因此容许在系统中建立更多的线程来提高并发程度。
- 线程的创建时间比进程短;
- 线程的终止时间比进程短;
- 同进程内的线程切换时间比进程短;
- 由于同进程内线程间共享内存和文件资源,可直接进行不通过内核的通信;
-
线程(轻型进程)是 CPU 运用的一个基本单元,包括
- 程序计数器
- 寄存器集
- 栈空间
-
一个线程与它的对等线程共享:
- 代码段
- 数据段
- 操作系统资源
总体作为一个任务
-
传统的或重型进程等价于只有一个线程的任务
# 进程和线程的比较
- 并发性:在引入线程的 OS 中,不仅进程之间可以并发执行,而且在一个进程中的多个线程之间亦可并发执行,因而使 OS 具有更好的并发性,从而能更有效地使用系统资源和提高系统吞吐量。
- 拥有资源:进程是拥有资源的独立单位
- 系统开销:在创建或撤消进程时,系统都要为之分配或回收资源,如内存空间、I/O 设备等。因此,OS 所付出的开销将明显地大于在创建或撤消线程时的开销。
- 地址空间和其他资源(如打开文件):进程间相互独立,同一进程的各线程间共享--某进程内的线程在其他进程不可见
- 通信:进程间通信 IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信--需要进程同步和互斥手段的辅助,以保证数据的一致性
- 调度:线程上下文切换比进程上下文切换要快得多;
# 益处
- Responsiveness
响应度高:一个多线程的应用在执行中,即使其中的某个线程阻塞,其他的线程还可继续执行,从而提高响应速度 - Resource Sharing
资源共享:同一进程的多个线程共享该进程的内存等资源 - Economy 经济性:创建和切换线程的开销要低于进程。比如,Solaris 中进程创建时间是线程创建的 30 倍,进程切换时间是线程切换的 5 倍。
- U- tilization of MP Architectures
MP 体系结构的运用:多线程更适用于多处理机结构。 - Kernel-supported threads 内核支持的线程 (Mach and OS/2).
- User-level threads; supported above the kernel, via a set of library calls at the user level (Project Andrew from CMU). 用户级线程;在内核之上,通过用户级的库调用
- Hybrid approach implements both user-level and kernel-supported threads (Solaris 2). 混合处理实现用户级和内核支持线程
# 用户和内核线程
# 内核线程
-
Supported by the Kernel 由内核支持,在内核空间执行线程创建、调度和管理
Thread is unit of CPU scheduling -
依赖于 OS 核心,由内核的内部需求进行创建和撤销,用来执行一个指定的函数。Windows NT 和 OS/2 支持内核线程;
-
例子
- Windows XP/2000
- Solaris
- Digital UNIX
-
内核维护进程和线程的上下文信息;
-
线程切换由内核完成;
-
一个线程发起系统调用而阻塞,不会影响其他线程的运行。
-
时间片分配给线程,所以多线程的进程获得更多 CPU 时间。
# 用户线程
- 由用户级线程库进行管理的线程
- 线程库提供对线程创建 \ 调度和管理的支持,无需内核支持。
- 例子:
- POSIX Pthreads
- Mach C-threads
- Solaris threads
- 不依赖于 OS 核心,应用进程利用线程库提供创建、同步、调度和管理线程的函数来控制用户线程。调度由应用软件内部进行,通常采用非抢先式和更简单的规则,也无需用户态 / 核心态切换,所以速度特别快。
- 用户线程的维护由应用进程完成;
- 内核不了解用户线程的存在;
- 用户线程切换不需要内核特权;
- 用户线程调度算法可针对应用优化;
- DRAWBACKS:
如果内核是单线程的,那么一个用户线程发起系统调用而阻塞,则整个进程阻塞。
时间片分配给进程,多线程则每个线程就慢。
# 用户线程与内核线程
- 调度方式:内核线程的调度和切换与进程的调度和切换十分相似,用户线程的调度不需 OS 的支持。
- 调度单位:用户线程的调度以进程为单位进行,在采用时间片轮转调度算法时,每个进程分配相同的时间片。对内核级线程,每个线程分配时间片。
# 多线程模型
指内核线程和用户线程之间
# 多对一
- 多个用户级线程映像进单个内核线程
- 任一时刻只能有一个线程可以访问内核 (并发性低)
- 一个用户线程发起系统调用而阻塞,则整个进程阻塞。
# 一对一
- 每个用户级线程映像进内核线程
- Allowing another thread to run when a thread makes a blocking system call
- 提供了更好的并发性,一个用户线程发起系统调用而阻塞时允许另一个线程运行
- 每创建一个用户级线程需创建一个相应的内核线程,带来了额外开销,所以许多系统限制应用中的线程数目
- 例如:Windows95/98/NT,OS/2
# 多对多
- 多对一模型的缺点:不能实现真正的并发
- 一对一模型的缺点:需限制应用中的线程数目
- Many-to-many model multiplexes many user-level threads to a smaller or equal number of kernel threads.
- 多对多模型:不限制应用的线程数、多个线程可以并发
# Two-level Model
- Similar to M:M, except that it allows a user thread to be bound to kernel thread
# Threading Issues
- Semantics 语义 of fork () and exec () system calls
- Thread cancellation
- Signal handling 信号处理
- Thread pools 线程池
- Thread specific data 线程特定数据
# fork and exec system calls
- fork is used to create a separate, duplicate process
- in multithreaded program, some UNIX have two versions of fork:
- duplicate all threads in the process 复制所有线程
- used when no following exec call
- duplicate only the thread calling fork 只复制调用 fork 的线程
- used when a immediately followed exec call
- duplicate all threads in the process 复制所有线程
- exec replaces the entire process, including all threads
exec 参数所指定的程序会替换整个进程
# Two cancellation scenarios
- target thread: the thread to be cancelled
- asynchronous cancellation 异步取消: one thread immediately terminates the target thread 一个线程立即终止目标线程
- may be cancelled in the middle of updating data shared with other threads
- may not free a system-wide resource
- deferred cancellation 延迟取消: the target thread can periodically check if it should terminate (at so called cancellation points in Pthread) 目标线程检查它是否应该终止
# signal delivery
-
当特定事件发生时,一般会给进程发送信号来通知
-
信号可以被同步或异步的接收
-
同步信号会发送到产生信号的同一个进程。比如,非法内存访问,或者除零错。
-
当信号是由运行进程之外的事件所产生 ,那么进程就异步的接收信号。比如,定时器中断或者 ctrl+C。
-
in single-thread programs, signals are always delivered to a process 单线程进程中,信号总是发送给进程
-
in multithreaded programs, where should a signal be delivered?
在多线程进程中,信号被发送到哪里? -
同步信号需要发送到产生信号的线程,而不是进程中的其他线程
-
异步信号??
- the thread to which the signal applies 信号适用的线程
- every thread in the process 进程中的所有线程
- certain threads in the process 进程的某些线程
- a specific thread to receive all signals 一个特定线程用来接收所有信号
-
Some asynchronous signals such as ctrl+C should be sent to all threads
有些信号,如 ctrl+C 应该发送给所有的线程 -
Allow a thread to specify which signals it will accept and which it will block.
允许线程描述它会接收的信号和拒绝的信号,则信号只发给那些不拒绝它的线程,由于信号只能被处理一次,一般将信号发给进程中不拒绝它的第一个线程 -
Allow a signal to be delivered to a specified thread
允许信号发送给一个特定线程,该线程专门处理信号
# Thread pools
- Why the thread pools? 为什么要线程池
- avoid creation and termination overhead, so that it is faster to service a request 避免创建和撤销开销
- put bound on number of threads, thus limit CPU and memory usage 限制线程的数量
- Main idea of thread pools
- 进程创建时就创建一定数量的线程,放入线程池中等待;
- 进程收到请求时,唤醒线程池中的一个线程进行处理;
- 线程完成工作,返回线程池中继续等待;
- 如果没有可用的线程,进程就等待直到有空线程为止
- 线程池限制了线程的数量
- 用现有线程提供服务比创建线程快
- 所有线程只创建和撤销一次,在线程池中复用,减小了开销
- considerations
- number of CPUs
- amount of physical memory
- expected number of concurrent requests
# Thread-Specific Data
- threads belonging to a process share the data of that process
- thread-specific data 线程特定数据 is a independent copy of certain data owned by one thread
- example
- in a transaction processing system, service each transaction in a separate thread
- transaction ID to each thread
- supported in most thread libraries including Win32, Pthreads, Java
# Solaris2 线程
- Solaris 支持内核线程 (Kernel threads)、轻权进程 (Lightweight Processes) 和用户线程 (User Level Threads)。一个进程可有大量用户线程;大量用户线程复用少量的轻权进程,不同的轻权进程分别对应不同的内核线程。
- Solaris 2 is a version of UNIX with support for threads at the kernel and user levels, symmetric multiprocessing, and real-time scheduling.
Solaris 2 是 UNIX 的一个版本,支持内核级和用户级线程,对称多处理和实时调度 - LWP – intermediate level between user-level threads and kernel-level threads.
LWP 在用户级线程和内核级线程之间层次
# 轻权进程 (LightWeight Process)
- 它是内核支持的用户线程,是内核数据结构,驻留在内核空间。一个进程可有一个或多个轻权进程,每个轻权进程由一个单独的内核线程来支持
- 由内核调度程序进行调度
- 允许一个进程中发出多个并发的系统调用 (因为有多个线程
- 由线程库进行管理和调度
# 线程举例
- 用户级线程在使用系统调用时(如文件读写),需要 “捆绑 (bound)” 在一个 LWP 上。
- 永久捆绑:一个 LWP 固定被一个用户级线程占用,该 LWP 移到 LWP 池之外
- 临时捆绑:从 LWP 池中临时分配一个未被占用的 LWP
- 对于没有绑定的 LWP,则由线程库动态地进行调整:
- 一个进程对应的 LWP 组成 LWP 池,线程库动态调整池中 LWP 的数目,以保证应用的最佳性能:
- 当池中的 LWP 全部阻塞,而进程中还有线程可以运行,则线程库会为之创建另一个 LWP;
- 当一个 LWP 长时间没用(老化,一般为 5 分钟),则线程库会删除它。
- 一个进程对应的 LWP 组成 LWP 池,线程库动态调整池中 LWP 的数目,以保证应用的最佳性能:
- 在使用系统调用时,如果所有 LWP 已被其他用户级线程所占用(捆绑),则该线程阻塞直到有可用的 LWP--例如 6 个用户级线程,而 LWP 池有 4 个 LWP
- 如果 LWP 执行系统调用时阻塞(如 read () 调用),则当前捆绑在 LWP 上的用户级线程也阻塞。
# WindowsNT
就绪状态 (Ready):进程已获得除处理机外的所需资源,等待执行。
备用状态 (Standby):特定处理器的执行对象,系统中每个处理器上只能有一个处于备用状态的线程。
运行状态 (Running):完成描述表切换,线程进入运行状态,直到内核抢先、时间片用完、线程终止或进行等待状态。
等待状态 (Waiting):线程等待对象句柄,以同步它的执行。等待结束时,根据优先级进入运行、就绪状态。
转换状态 (Transition):线程在准备执行而其内核堆栈处于外存时,线程进入转换状态;当其内核堆栈调回内存,线程进入就绪状态。
终止状态 (Terminated):线程执行完就进入终止状态;如执行体有一指向线程对象的指针,可将线程对象重新初始化,并再次使用。
初始化状态 (Initialized):线程创建过程中的线程状态;
# NT 线程有关 API
CreateThread () 函数在调用进程的地址空间上创建一个线程,以执行指定的函数;返回值为所创建线程的句柄。
ExitThread () 函数用于结束本线程。
SuspendThread () 函数用于挂起指定的线程。
ResumeThread () 函数递减指定线程的挂起计数,挂起计数为 0 时,线程恢复执行。
# Thread library
Thread library provides programmer with API for creating and managing threads
Two primary ways of implementing
Library entirely in user space
Kernel-level library supported by the OS
Examples:
POSIX Pthreads 支持用户级和内核级线程库
Win32 threads 支持内核线程
Java thread
# Java 线程
- Java threads are managed by the JVM
- Typically implemented using the threads model provided by underlying OS
- On windows system, java threads are implemented using Win32 API
- On UNIX or Linux, use Pthreads
- Java Threads May be Created by: Java 线程可如下创建:
- Extending Thread class
扩充线程类 - Implementing the Runnable interface
实现可运行接口
- Extending Thread class
# 线程类型的扩展
1 | class Worker1 extends Thread |
# 创建线程
1 | public class First |
# 可运行接口
1 | public interface Runnable |
# Interface 可运行接口的实现
1 | class Worker2 implements Runnable |
# 创建 run 线程
1 | public class Second |
# Java 线程的管理
- suspend () – suspends execution of the currently running thread. 挂起 - 暂停当前线程的运行
- sleep() – puts the currently running thread to sleep for a specified amount of time.
睡眠 - 让当前线程入睡一段指定的时间 - resume() – resumes execution of a suspended thread.
恢复 - 再执行被挂起的线程 - stop() – stops execution of a thread.
停止 - 停止一个线程的执行
# java 线程状态
# 生产者消费者问题
1 | public class Server { |
# 生产者线程
1 | class Producer extends Thread { |
# 消费者线程
1 | class Consumer extends Thread { |