1. 线程的原理

1.1 线程的起源

现在意义上的线程是轻量级的进程,是进程中指令执行路径的描述,是多个寄存器等变量条件就绪等待 CPU 选择执行的状态,它包含着私有的程序计数器等状态来确定指令的执行顺序。线程是 CPU 利用的最小单元。

通常,线程和并发联系紧密,在单处理器环境下,多个线程只能并发。具体的,将一个进程切分为多个代码段,每个代码段看做一个线程,这多个线程轮流的使用 CPU,并发的执行。

  • 线程发明的历史表明,在早期并没有线程的命名,那时将一连串的控制流程称为 process。这时的 process 是可以共享内存空间,通过共享变量,旗语来交流。
  • 到后来的 Unix 中process 的概念变为由一连串的控制指令执行过程加上虚拟地址空间。这变得很笨重。它们有各自的地址空间,不共享一个内存空间,只能通过管道、信号来交流,共享内存(更为庞杂的机制)是后续才加上的。
  • Unix用户开始想念早前的 process 的方便,它们可以方便的共享内存空间,于是Thread的名称出现了,是早期 processes 共享一个Unix意义下的 process的内存空间,相对于 Unix 下process 的笨重,thread 是轻量级的。Thread命名在操作系统领域中诞生了。

因此发明thread的动机是:让轻量级的 processes 可以共享资源,方便操作。线程之间的切换的代价比进程之间切换要小得多。

1.2 线程是并行的吗?

对于单核系统,事实上,同一个时刻CPU 只能运行一个线程。当一个进程包含多个线程,人们也许会感觉到线程是同时工作的,这是 CPU 快速切换不同线程工作的结果。比如:在 Word 中输入文字,自动调整格式和显示文字是两个线程,在单核中,CPU 会迅速切换工作,让用户感觉文字在输入后就自动的按照格式显示了,像是在同时进行。这是人的感官层次上的并行。

对于多核系统,两个线程同时在不同核运行时可以的,这是真正意义上的操作系统层次的并行。这使得多核计算机可以最大化利用率。


image caption

1.3 线程是并发的

  • 并发: 当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间 段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状。.这种方式我们称之为并发(Concurrent)。

    • 并发,在操作系统中,是指一个时间段中有几个线程都处于已启动运行到运行完毕之间,且这几个线程都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。
  • 并行:当系统有一个以上CPU时,则线程的操作有可能非并发。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。

  • 区别:并发和并行是即相似又有区别的两个概念,并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔内发生。在多道程序环境下,并发性是指在一段时间内宏观上有多个程序在同时运行,但在单处理机系统中,每一时刻却仅能有一道程序执行,故微观上这些程序只能是分时地交替执行。倘若在计算机系统中有多个处理机,则这些可以并发执行的程序便可被分配到多个处理机上,实现并行执行,即利用每个处理机来处理一个可并发执行的程序,这样,多个程序便可以同时执行。

1.4 管理线程

操作系统需要知道线程的一切信息,便于对线程进行有效的管理。每个线程就是一个特定的指令执行序列,需要有自己私有的资源,这些对每个线程都是私有的信息放在线程控制表。同时,线程共享同一个进程的地址空间,这个由进程管理即可。就像宿舍中,有些设备、物品是共享的,有些东西是私有的。

原则是,让共享的资源在保证线程正常工作的情况下越多越好。私有资源可以保证一个线程能够完成它的指令执行序列。程序计数器 PC 肯定要私有,不然线程都不知道下一个它该执行的指令,寄存器、栈、状态字这些都包含着指令执行的变量信息,CPU 状态,如果被其他线程共享改动会使本线程不能正常运行。

下图显示了当有进程中有多线程时线程私有资源和公有资源,以及说明:

1.4 线程的实现方式

线程的实现是通过不同的管理方式来实现。

用户态线程

由进程自己管理线程,内核中都不知道多个线程的存在,只能意识到只有一个正在运行的线程。对于进程,每个进程都会有一个线程库,其中包含的代码可以做到创建线程、销毁线程,线程之间的交流,线程执行的调度,线程状态的恢复等。

优点:

  • 线程之间的切换不需要内核权限
  • 因为和内核无关,可以在任何操作系统中运行
  • 更快的创建和管理线程

缺点:

  • 因为不能和内核有关系,所有大部分的系统调用被禁止。
  • 编程变得很复杂,一方面要推动进程的执行,一方面又要处理好线程的调度。

  • 因为多个线程对于内核而言它只能意识到在同不同时刻只有一个线程在运行,意识不到多个线程的存在,因此即使在多处理系统(Multiprocessing)中,内核也不会同时分配多个 CPU 来运行同一个进程中的多个线程。

内核态线程

由操作系统内核管理线程,线程控制块放在内核中,拥有线程的所有资料,这样操作系统就同时掌握进程控制块和线程控制块,因此可以对线程进行调度、资源分配,安全措施,这些都是内核自动完成,不需要用户在程序中设定这种调度、分配,当在多处理(Multiprocess)环境中,内核意识到一个进程中有多个线程,可以同时用多个 CPU 来运行多个线程。因而,任何程序都可以被编写为多线程,在用户编写的程序进程中不存在管理线程的代码,因为内核会自动的采取调度方法来最大效率的并行执行线程。

优点:

  • 在多进程中,内核可以根据掌握的所有不同进程中的线程进行统一调度。
  • 因为内核监视着线程的执行,当一个线程阻塞,内核可以调度此进程中的另一个线程继续工作。

缺点:

  • 效率低,每次线程切换都要从用户态陷入到内核态,由内核来调度。
  • 占用内核空间
  • 和操作系统直接相关,不能在不同的内核上运行。

1.5 多线程模型

现代操作系统是用户态线程和内核态线程的组合模型。这使得对同一个应用程序,在多处理器操作系统中,它的多个线程也能并行(时有对一个进程有多个内核态线程可以服务时,才会有并行)。如下,在多处理器环境下,多对多和一对一都可以做到一个进程中线程的并行,因为内核可以选择同时运行同一个进程的用户态线程对应的内核态线程。注意,只有内核态线程可以并行。

  • 多对多模型(M:N, M>=N, M 用户态线程个数,N 内核态线程个数)

  • 多对一模型(M:1)

  • 一对一(进程中的一个用户态线程对应一个内核态线程)(1:1)

内核态的多个线程才能具备并行的能力,用户态多个线程不具备这个能力。用户态的多个线程要想也能够并行的执行,必须告知内核它有多个线程需要执行,而办法是通过内核态线程来和用户态线程取得某种联系。内核态进程可以看做是用户态进程与内核沟通的桥梁。

1.6 常见疑惑

  • 在多处理器上系统中,不同进程的线程能否并行?

    不能!首先即使在多处理器环境下,进程调度的目的就是最大化的考虑进程响应时间、CPU 利用率、CPU 占用公平性,同一时刻只会有一个进程在使用 CPU。

  • 在多处理器上系统中,同一个进程的线程能否并行?

    可以!前提是线程模型采用的是多对多或1对1,借助于内核态线程达到同一个进程中多线程的并行。

1.7 参考

https://stackoverflow.com/a/5201879/6170444

https://www.geeksforgeeks.org/operarting-system-thread/

http://www.serpentine.com/blog/threads-faq/the-history-of-threads/

https://www.tutorialspoint.com/operating_system/os_multi_threading.htm

https://cs162.eecs.berkeley.edu/

https://www.youtube.com/watch?v=5Ip__MiHRxw&index=3&list=PLggtecHMfYHA7j2rF7nZFgnepu_uPuYws

https://blog.csdn.net/claram/article/details/52094587

https://stackoverflow.com/questions/41647778/does-user-level-threads-take-advantage-of-multiprocessing

https://www.geeksforgeeks.org/operating-system-difference-multitasking-multithreading-multiprocessing/