为什么使用线程
我们需要一种新的实体(线程),既能够满足不同实体之间可以并发执行,同时可以共享相同的地址空间和文件资源等,相对于进程,减少创建和切换时的开销。
什么是线程
Thread: 进程当中的一条执行流程
从两个维度来理解进程:
- 从资源组合的角度:进程把一组相关的资源组合起来,构成了一个资源平台(环境),包括地址空间(代码段,数据段),打开的文件等各种资源。
- 从运行的角度:代码在这个资源平台上的一条执行流程。(这个执行流程既是线程 )
即:线程 = 进程 - 共享资源 (完成一个控制流的管理)
1. 线程优点:
- 一个进程中可以同时存在多个线程
- 各个线程之间可以并发执行
- 各个线程之间可以共享地址空间和文件等资源
2.线程缺点:
- You have to explicitly coordinate access to shared date with locks. If you forget a lock, you’ll end up with corrupted data. (共享数据需加锁)
- Circular dependencies amongst locks can lead to deadlocks. (导致死锁)
- They are hard to debug with subtle timing issues. (执行时序不确定,难以调试)
- Callbacks don’t work with locks. (回调不适用于锁)
- It’s hard to get good performance. (很难有好的表现)
- They are “too hard for most programmers to use, and even for experts development is painful.” (难以使用)
上述缺点是 1995 年 John Ousterhout
在一个演说中提到的,因为当时时间较早,所以其中有些缺点,比如说难用,回调不适用于锁 (有待考证),表现不好等,现在可能已经克服了,像 Linux
系统中 NPTL
模型实现的 pthreads
库,以及其他语言实现的多线程库,例如:java
线程库,封装的都很好,使用起来也都十分方便; 但是 共享数据需加锁
,死锁
, 执行时序不确定,难以调试
,这三点依然是现在多线程编程中面临的最大挑战。
Attention:线程和进程比较:
- 进程是资源分配单位,线程是CPU调度单位;
- 进程拥有一个完整的资源平台,而线程只独享必不可少的资源,如寄存器和栈,来达到独立的控制流;
- 线程同样具有就绪,阻塞和执行三种基本状态,同样具有状态之间的转换关系;
- 线程能减少并发执行的时间和空间开销:
- 线程的创建时间比进程短; - 线程的终止时间比进程短; - 同一进程内的线程切换时间比进程短; - 由于同一进程的各线程间共享内存和文件资源,可直接进行不通过内核的通信
线程的实现
传统的操作系统主要有三种线程的实现方式:
1. 用户线程:在用户空间实现1
2
3
4
5
6
7
8
9
10
11
12POSIX Pthreads, Mach C-threads, Solaris threads
在用户空间实现的线程机制,它不依赖于操作系统的内核,由一组用户级线程库函数来完成线程的管理,
包括线程的创建,终止,同步和调度等。
* 缺点
1. 一个线程发起系统调用而阻塞,则整个进程等待
2. 当一个线程运行,除非主动交出CPU使用权,否则它所在进程中的其他线程将无法运行
3. 由于时间片是分给进程,故与其他进程相比,在多线程执行时,每个线得到的时间片较少,执行会比较慢
* 优点
1. 无需用户态和内核态切换,速度快
2. 允许每个进程拥有自己的调度算法
2. 内核线程:在内核中实现1
2
3windwos ,Solaris, Linux
指在操作系统内核当中实现的一种线程机制,由操作系统的内核来完成线程的创建,终止和管理,开销较大
3. 轻量级进程:在内核中实现,支持用户线程1
2
3Solaris (LightWeight Process)
内核支持的用户线程。一个进程可以有一个或者多个轻量级进程,每个轻量级进程由一个单独的内核线程来支持。
Linux 系统线程实现
那么最新版本的 Linux
系统中的 pthreads
线程库中的线程是如何实现的呢 ? 我参考了一下书籍 << Linux 内核设计与实现>>
以及 Linux Programmer's Manual
当中的描述, 实现流程大致如下:
Linux Programmer’s Manual 描述的实现:
1 | Linux implementations of POSIX threads |
在 Linux 2.6 kernel
版本后, 采用的是 NPTL
模型来实现。该模型是一个所谓的 1×1
线程库,其中由用户创建的线程(通过pthread_create()
库函数)与内核中的 可调度实体(Linux
中的任务)进行1-1对应,是十分简单的线程实现方式。
假如我们要在一个进程中创建四个线程, 在 Linux 系统中,实现思路是:创建四个进程并分配四个普通的 task_struct
(PCB
在代码中的表示) 结构, 然后在建立这个四个进程时,指定它们共享某些资源即可。 也就是说 Linux
系统中创建进程和线程是类似的,最终都是调用 clone()
,只是对外封装接口表现不一样,传入参数不一样而已。
参考
- https://www.bilibili.com/video/av6538245/?p=2
- https://blog.acolyer.org/2014/12/09/why-threads-are-a-bad-idea/
- 书籍: << Linux 内核设计与实现 >> -Robert Love