目录

1.操作系统教材上的定义

2.做个假设

3.重新定义进程和线程

线程

进程

结论

如何理解以前的进程?

4.如何管理线程?

Windows的做法

Linux的做法

从CPU的角度理解: 线程、进程、执行流

举例理解进程和线程的关系

5.理解: 线程目前分配资源,本质就是分配地址空间范围

前置知识: 80386的虚拟地址转到物理地址

6.如何让每个线程执行不同的代码?

7.理解线程比进程更加轻量化

创建和释放更加轻量化

切换更加轻量化

8.主线程和新线程

9.线程的优缺点

10.线程的用途

11.线程和进程共享的部分

12.线程也有自己私有的部分

13.结论


1.操作系统教材上的定义

线程定义:​ 是进程内的一个执行分支,而且线程的执行粒度,要比进程更细

2.做个假设

假设创建多个个”子进程”,和父进程共享一个地址空间,那么这些进程的task_struct就共用同一个进程地址空间

如果每个”子进程”都拥有进程地址空间的正文代码的一部分,那么这些”子进程”的就叫线程 → 每个线程只占用一部分正文代码,执行进程代码的一部分,得出线程的执行粒度要比进程更细

在Linux中,线程在进程“内部”执行,也就是说线程在进程的地址空间内运行,因为地址空间是进程的资源窗口

3.重新定义进程和线程

线程

原则上CPU是不需要知道执行的是进程还是线程,CPU只有调度执行流的概念

认为线程是操作系统调度的基本单位,一个进程可以有多个线程,换句话说,一个进程可以有多个执行流

注: 现代CPU一般是多核的,理论上CPU如果有n个物理核心,CPU就可以同时运行n个线程,但其实也有特殊情况: Intel的超线程(Hyper-Threading)技术允许n个物理核心运行多于n个线程

注: AMD也有类似超线程的技术,叫同步多线程SMT,全称为Simultaneous Multi-Threading,可以看看Simultaneous Multithreading: Driving Performance and Efficiency on AMD EPYC CPUs,这里不介绍

简单了解超线程技术

Intel官网https://www.intel.com/content/www/us/en/gaming/resources/hyper-threading.html有对超线程的简单介绍,这里截取一部分内容:

What Is Hyper-Threading?

Single thread processing

        Intel® Hyper-Threading Technology is a hardware innovation that allows more than one thread to run on each core(一个核心可以运行多个线程). More threads means more work can be done in parallel.

        How does Hyper-Threading work? When Intel® Hyper-Threading Technology is active, the CPU exposes two execution contexts per physical core(原因: 一个核心可以看到2个执行上下文). This means that one physical core now works like two “logical cores” that can handle different software threads(这样一个网物理核心相当于2个逻辑核心).

Two logical cores can work through tasks more efficiently than a traditional single-threaded core. By taking advantage of idle time when the core would formerly be waiting for other tasks to complete, Intel® Hyper-Threading Technology improves CPU throughput.

Multithread processing

Wondering how to enable Hyper-Threading? It’s enabled by default, but it can be switched on and off from the BIOS environment by setting “Hyper-Threading Technology” to “Enable” or “Disable”. Note that Intel® Hyper-Threading Technology is only available on some enthusiast CPUs: see the full list here.

What Benefits Will I See from Hyper-Threading?

Dual monitor PC graphic

With CPU Hyper-Threading, a PC can process more information in less time and run more background tasks without disruption. Under the right circumstances, the technology lets CPU cores effectively do two things at once. Multitaskers, streamers, and professionals running heavily threaded programs can boost their computing experience by upgrading to a gaming laptop or gaming desktop CPU with Intel® Hyper-Threading Technology.

打开Windows任务管理器,在性能那一栏,可以看到"内核"和"逻辑处理器":

内核指的是物理核心,逻辑处理器指的是逻辑核心,上图4核即代表物理上实际处理器个数,为了更好的并发,利用资源,逻辑上将一个核分为2个处理器,如一个核分时并发处理2个任务

这个逻辑处理器"logical processor"这个概念曾经在2007年的Intel® 64 and IA-32 Architectures
Software Developer’s Manual Volume 1: Basic Architecture 2.2.5 Hyper-Threading Technology提到过:

AMD在Simultaneous Multithreading: Driving Performance and Efficiency on AMD EPYC CPUs的图也可以说明超线程:

进程

进程是承担分配操作系统资源的基本实体,例如进程将得到的资源分配给每个线程,例如上方提到的每个线程只占用一部分正文代码(线程还占用其他东西,这里暂时不提)

而且执行流也是资源,线程是进程内部的执行流资源

结论

结论: 进程是执行流存在与运行所必需的资源与环境的抽象集合

如何理解以前的进程?

之前在OS17.【Linux】进程基础知识(1)文章讲过进程,当时认为: 

进程=内核数据结构对象(例如PCB,其实还有别的)+代码段+数据段

按照现在的观点: 操作系统以进程为单位来分配资源,那时的进程内部只有一个执行流

4.如何管理线程?

从上面的理解来看: 一个进程可以有多个线程,那么进程:线程==1:n

即大部分情况下,线程比进程数量多,而且线程也需要依靠操作系统来调度,那么操作系统也需要管理线程

如何管理线程?

答: 先描述再组织,描述线程可以使用结构体struct tcb(thread control block,线程控制块),但是这样做会特别复杂,因为之前光描述进程就费了很大的力气,现在又来描述线程,那么操作系统需要记录线程的大量信息: 例如这个线程属于哪个进程?线程属于哪个调度队列?......

Windows的做法

很久以前,微软公开过部分Windows的源码: Windows Research Kernel,简称WRK

这是当时的线程控制块的源码(由于篇幅,仅仅截取一部分):

typedef struct _KTHREAD {

    //
    // The dispatcher header and mutant listhead are fairly infrequently
    // referenced.
    //

    DISPATCHER_HEADER Header;
    LIST_ENTRY MutantListHead;

    //
    // The following fields are referenced during context switches and wait
    // operatings. They have been carefully laid out to get the best cache
    // hit ratios.
    //

    PVOID InitialStack;
    PVOID StackLimit;
    PVOID KernelStack;

    KSPIN_LOCK ThreadLock;
    union {
        KAPC_STATE ApcState;
        struct {
            UCHAR ApcStateFill[KAPC_STATE_ACTUAL_LENGTH];
            BOOLEAN ApcQueueable;
            volatile UCHAR NextProcessor;
            volatile UCHAR DeferredProcessor;
            UCHAR AdjustReason;
            SCHAR AdjustIncrement;
        };
    };
    //......
}

完整代码见:

https://github.com/x-tinkerer/WRK/blob/master/base/ntos/inc/ke.h#L1069

Linux的做法

Linux没有像Windows那么做,Linux认为线程也被调度,无非线程拥有的资源比进程,这个”先描述再组织”可以不需要创建新的数据结构,复用进程数据结构和管理算法,使用"进程"模拟的线程,创建线程会创建新的task_struct,没有线程控制块

复用进程数据结构和管理算法,维护线程的成本低,代码健壮性好,这样不需要维护两份类似的代码,是卓越的设计

无论是Windows的做法还是Linux的做法,都要遵守"操作系统书对线程的理论定义: 线程是进程内的一个执行分支,而且线程的执行粒度,要比进程更细",理论是理论,实践是实践,不同操作系统的有不同的方案

从CPU的角度理解: 线程、进程、执行流

CPU的角度: 线程(指真正有tcb的)<=执行流<=进程

1.这里的<=表示的是 从小到大的概念范围

2.线程是具体的操作系统对象,由CPU调度执行,线程是执行流的实例或载体,但执行流本身是一个更广的抽象概念

Linux中的线程是轻量级进程(Light-Weight Process,简称LWP)

3.CPU看到轻量级进程,那么执行流==线程,如果看到的是进程,那么执行流>=线程

举例理解进程和线程的关系

家庭中可以有很多人(这里家庭指进程,家中的每一个人指线程,一个进程可以有多个线程),每个人都有不同的任务(每个线程有不同的任务),例如上学上班等等,但对于家庭而言有一个共同的任务: 过好日子,(线程共同完成进程的任务,进程要为线程提供资源)

线程是进程的一个执行分支,那么线程在执行,本质上就是进程在执行

进程和线程的关系图:

5.理解: 线程目前分配资源,本质就是分配地址空间范围

前置知识: 80386的虚拟地址转到物理地址

以Intel的第一个32位处理器80386为例子:

80386 Programmer's Reference Manual的Chapter 5 Memory Management 5.2.2 Linear Adress这张图足矣说明,不需要解释

进程地址空间虽然是4G,32位的Linux中默认情况下内核空间是1G,由所有进程共享的,进程地址空间剩下的3G用户空间也不是全部都用,所以有些二级页表并不会创建,大部分情况下都不是全的

任何一个进程必须有页目录,页目录的表项不要求全部都用

这样就能理解: 线程目前分配资源,本质就是分配地址空间范围,例如,将代码段拆分后分配给各个线程,其他区域如果不划分,说明是所有线程共享的

6.如何让每个线程执行不同的代码?

放到不同的函数中执行

因为各个函数所处的地址范围不同,这样各个线程执行的代码就是分开的

7.理解线程比进程更加轻量化

创建和释放更加轻量化

线程比进程的创建和释放更加轻量化,创建线程一般只需要创建PCB(Linux没有线程控制块),不像创建进程需要创建PCB+创建地址空间……

切换更加轻量化

线程比进程的切换更加轻量化

线程切换和进程切换一样,都需要切换上下文,但是线程切换时,地址空间不需要切换(因为该进程所属的线程共享地址空间的绝大部分),进程切换需要切换地址空间,那么线程切换比进程切换的缓存cache刷新频率低,这样线程执行时缓存命中率更高

注: cat /proc/cpuinfo | grep cache命令可以看cache大小

当进程中的所有线程的时间片被耗尽时,进程也是要被切换的缓存cache中属于该进程的数据必须丢弃,更新为下一个需要执行的进程的数据

注意: 进程的时间片划分给进程所属的线程,这样才合理,如果所有进程的所有线程的时间片都一样,那么含线程数量多的进程执行时间长, 含线程数量少的进程执行时间短,这是不公平的

8.主线程和新线程

主线程是程序启动时自动创建的第一个线程,可以理解为之前在OS17.【Linux】进程基础知识(1)文章讲过的只有一个执行流的进程,

新线程(又称为副线程)是主线程创建出来的

9.线程的优缺点

优点:

1.创建一个新线程的代价要比创建一个新进程小得多
2.与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
3.线程占用的资源要比进程少很多
4.能充分利用多处理器的可并行数量
5.在等待慢速I/O操作结束的同时,程序可执行其他的计算任务(其实只有单个执行流的进程也能做到)

6.计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现(不是线程越多越好! 线程切换也是有成本的)
7.I/O密集型应用,为了提高性能,将I/O操作重叠,线程可以同时等待不同的I/O操作

缺点:

1.性能损失
一个很少被外部事件阻塞的计算密集型线程往往无法与其它线程共享同一个处理器,如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变


2.健壮性降低
编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的


3.缺乏访问控制
进程是访问控制的基本粒度,在一个线程中调用某些操作系统函数会对整个进程造成影响,例如一个线程访问了空指针可能导致整个进程崩溃

线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,该
进程内的所有线程也就随即退出


4.编程难度提高

编写与调试一个多线程程序比单线程程序困难得多

5.一个进程内的线程越多,每个线程分到的时间片越短

10.线程的用途

合理的使用多线程,能提高CPU密集型程序的执行效率
合理的使用多线程,能提高IO密集型程序的用户体验,例如下载器的边下边播功能是多线程运行的一种表现

11.线程和进程共享的部分

进程的多个线程共享同一地址空间,因此数据段和代码段都是共享的,

如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:
        文件描述符表
        每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)
        当前工作目录        
        用户id(UID)和组id(GID)

12.线程也有自己私有的部分

必答这2点:

1.线程有自己独立的上下文,一组寄存器是其中一部分,因为线程也要切换,线程是调度的基本单位

2.线程有自己私有栈: 用于存储局部变量、函数调用信息和返回地址等,保证执行流不会错乱

        →程序员给一个线程分配一个函数,这个函数的内部可能会调用其它的函数,因此必须保存调用链

画图表示:

(摘自http://www.csc.villanova.edu/~mdamian/threads/posixthreads.html)

补充3点: 线程有自己私有的线程id、errno、信号屏蔽字(block表)、调度优先级

13.结论

结论: 1.线程定义:​ 是进程内的一个执行分支,而且线程的执行粒度,要比进程更细 

         2.进程是执行流存在与运行所必需的资源与环境的抽象集合,进程包含了线程、task_struct、虚拟地址空间、进程的代码和数据,线程是进程内部的执行流资源
         3.线程是进程的一个执行分支,那么线程在执行,本质上就是进程在执行
         4.主线程是程序启动时自动创建的第一个线程,新线程(又称为副线程)是主线程创建出来的

        5.线程是操作系统调度的基本单位,一个进程可以有多个线程,换句话说,一个进程可以有多个执行流

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐