• linux线程详解:线程概念、线程调度、线程安全、线程模型


    1、线程与进程的区别

    (1)线程是轻量级的进程,是程序执行流的最小单位;
    (2)进程是资源分配的最小单位,线程是调度的最小单位;
    (3)进程可以创建线程,线程不可以创建进程;
    (4)一个进程由一个或者多个线程组成;
    (5同进程的线程间可以自由通信;
    (6)不同的进程间通信,必须用进程间的通信方法(比如:共享内存、管道等),效率比线程间通信低且麻烦;

    2、线程的定义

    图片:p42

    (1)线程的组成:线程ID、当前指令指针(PC)、寄存器集合、堆栈;
    (2)一个进程包含一个或者多个线程,其中进程的大部分资源(代码段、数据段、堆段、打开文件/信号等)都是线程间共享的,线程会保留一部分私有数据(寄存器、栈);

    3、线程的访问权限

    虽然同进程内的线程之间数据访问很自由,但是每个线程也拥有自己的私有存储空间,包括以下方面:
    (1)栈空间;
    (2)线程局部存储(Thread Local Storage, TLS):某些操作系统为线程单独提供的私有空间,通常只具有很有限的容量;
    (3)寄存器(包括PC寄存器):寄存器是执行流的基本数据,因此为线程私有;

    4、使用多线程的好处

    (1)某个操作可能会陷入长时间等待,如果是单线程则进入睡眠,无法进行执行;如果是多线程,则可以让执行这个操作的线程睡眠,让CPU去执行其他任务的线程;
    (2)某个操作会消耗大量的时间,如果只有一个线程,则线程会一直执行这个操作,导致程序和用户之间的交互中断;如果是多线程,则可以一个线程负责计算,另一个线程负责交互,两个线程交替在CPU上执行;
    (3)现在的CPU基本都是多核的,本身就具备同时执行多个线程的能力,因此单线程是无法全面发挥计算机的性能;
    (4)想较与多进程应用,多线程在数据共享方面的效率要高很多,编写代码也更方便;
    补充:在嵌入式开发中,能用多线程解决就不用多进程;

    5、线程的调度

    5.1、线程的状态

    在这里插入图片描述

    状态含义
    运行态线程正在CPU上运行
    就绪态线程已经具有除CPU之外的所有资源,等待分配到CPU就可以运行
    阻塞态线程等待某一个事件的发生或者等待得到某样资源(除CPU资源外),无法被执行

    (1)上图展示的是线程最基本的三种状态(运行态、就绪态、等待态也叫阻塞态),实际的线程状态比这个要复杂(比如还有挂起态),这里不展开讲;
    (2)运行态->就绪态:线程在CPU上运行,运行至时间片耗尽会被剥夺CPU的使用权,CPU会去执行其他的线程,而该线程进入就绪态,等待被CPU再次调度;
    (3)运行态->阻塞态:线程在CPU上运行,在时间片耗尽之前因为缺少某样资源而无法继续执行从而阻塞,线程会变为阻塞态,CPU会去调用其他的线程;
    (4)阻塞态->就绪态:导致线程阻塞的事件发生,线程具有了除CPU意外的所有资源,线程变为就绪态,等待被CPU调度;
    (5)就绪态->运行态:线程具有了除CPU意外的所有资源,被CPU调度从而在CPU上运行,此时线程变为了运行态;
    总结:线程在几种状态间的转换就叫做线程调度;

    5.2、线程优先级

    (1)在linux中线程分为0~99的100个优先级,数值越大优先级越高,linux中有专门的API设置线程的优先级;
    (2)给线程设置优先级是为了提高响应速度,改善用户的使用体验;
    (3)一般来说,跟用户交互的线程优先级要设置的高一些,后台运行的线程优先级设置的低一些;比如:你肯定不希望点一下鼠标要几秒才有反应,所以检测鼠标事件的线程优先级要设置的高一些;
    (4)优先级高的线程会优先被CPU调度,但是需要注意不能产生饿死现象,理想的情况:高优先级的线程会先运行,低优先级的线程稍后运行,但不能让低优先级的线程被饿死;

    5.3、线程的调度策略

    (1)SCHED_OTHER:分时调度策略;
    (2)SCHED_FIFO:实时调度策略,先到先服务。一旦占用cpu则一直运行,一直运行直到有更高优先级任务到达或自己放弃;
    (3)SCHED_RR:实时调度策略,时间片轮转。当进程的时间片用完,系统将重新分配时间片,并置于就绪队列尾。放在队列尾保证了所有具有相同优先级的RR任务的调度公平;

    5.4、线程调度算法

    (1)先来先服务:操作系统维护一个等待队列,需要CPU的线程就加入等待队列进行排队,操作系统按照先后顺序去调度线程。算法实现很简单,但是没有考虑线程执行的任务之间存在轻重缓急;
    (2)最短作业优先:选择执行时间最短的作业最先执行;
    (3)最短剩余时间优先:调度程序总是选择其剩余运行时间最短的那个进程运行,
    (4)最高响应比优先算法:为每个线程计算出响应比,响应比高的优先被运行,为了解决饿死的线程,线程的响应比是动态变化的,总体趋势: 每个作业随着在后备池等待时间的增长其响应比也不断增长,而且,预计运行时间越短的作业响应比增长越快;
    (5)轮转法:将CPU的运行划分成时间片,每个线程在CPU上运行直到时间片耗尽,时间片耗尽后就调度其他就绪态的线程在CPU上运行;
    (6)最高优先级算法:调度每次将CPU分配给具有最高优先级的就绪线程;
    (7)多级反馈队列算法:结合了前面的好几种算法,比前面介绍的调度算法都更优秀,比较复杂,这里不详细介绍;

    5.5、可抢占线程和不可抢占线程

    (1)抢占:线程在用尽时间片后被强制剥夺CPU的使用权,进入就绪态;
    (2)不可抢占线程:除非线程主动放弃CPU使用权或者进入阻塞态时,CPU才会被让渡给其他线程,否则其他线程将无法运行;
    (3)可抢占线程:时间片耗尽就让渡出CPU;
    补充:现在基本都是可抢占的线程;

    6、线程安全

    6.1、引起线程安全问题的原因

    (1)多个线程对共享资源进行操作时,由于线程之间感知不到其他线程对共享资源的操作,所以可能出现线程对共享资源的访问冲突;
    (2)比如:很经典的例子,两个线程对同一个int型变量i各进行"++"操作一百次,最后发现i的值不是200;

    6.2、线程同步的方法

    在这里插入图片描述

    (1)二元信号量:特殊情况的信号量,信号量的值是1,只有两种状态(占用与非占用),被锁住的资源同时只允许一个线程操作;
    (2)多元信号量:信号量的值大于1,允许多个线程访问共享资源,通过P操作和V操作来进行线程间同步;
    (3)互斥量:和二元信号量类似,资源仅同时运行一个线程访问,不同之处在于信号量可以被任意线程获取或释放,而互斥量只能谁获取谁释放;
    (4)临界区:比互斥量更严格,只有创建临界区的进程才能获得锁,其他进程无法获取该锁。把获取临界区的锁称为进入临界区,把释放临界区的锁称为离开临界区;
    (5)读写锁:适合频繁读取偶尔写入的情况,允许读状态时共享,写状态时独占;
    (5)条件变量:线程可以等待某个条件变量,当条件变量不满足条件时,线程进入睡眠;线程也可以去唤醒条件变量,当条件变量满足条件时,等到条件变量的线程都会被唤醒;

    6.3、可重入函数与不可重入函数

    6.3.1、两者的区别

    (1)函数重入:函数没有执行完成,由于外部因素或内部调用,又一次进入该函数执行;
    (2)区别:可重入函数被多个线程同时调用不会出错,不可重入函数只允许同时被一个线程调用;
    (3)可重入函数是线程并发执行的安全保障,在进行多线程编程时需要注意线程是否可重入;

    6.3.2、可重入函数要满足的条件

    (1)不使用任何(局部)静态或全局的非const变量;
    (2)不返回任何(局部)静态或全局的非const变量的指针;
    (3)仅依赖于调用方传入的参数;
    (4)不依赖任何单个资源的锁;
    (5)不调用任何不可重入的函数;

    7、三种算法模型

    内核线程和用户态线程

    (1)我们调用内核的API创建的是用户态线程,不是内核态线程;
    (2)内核线程负责管理和调度用户态的线程;
    (3)用户态线程并不一定在操作系统里对应相等数量的内核线程;

    7.1、一对一模型

    在这里插入图片描述

    优点:
    (1)一个用户态线程对应一个内核态线程,使得用户态的线程具有了和内核线程一致的优点,用户线程直接实现真正的并发;
    (2)一个用户态线程因为某些原因阻塞时,其他用户态线程执行不会收到影响;
    缺点:
    (1)操作系统限制了内核线程的数量,因此一对一模型会让用户的线程数量受到限制;
    (2)操作系统的内核线程调度,上下文切换的开销较大,导致用户线程的执行效率下降;

    7.2、多对一模型

    在这里插入图片描述

    优点:
    (1)将多个用户态线程映射到一个内核线程上,用户态线程之间切换开销小,执行效率高;
    (2)多对一模型使得用户态线程可以无限制的增加;
    缺点:
    (1)当一个用户态线程阻塞时,映射到同一个内核线程的用户态线程全部阻塞;

    7.3、多对多模型

    在这里插入图片描述

    多对多模型结合了多对一和一对一模型的特点,克服了缺点保留了优点,将多个用户态线程映射到少数但不止一个内核线程上;
    优点:
    (1)一个用户态线程阻塞不会使得所有用户态线程阻塞,因为还有别的内核线程可以调度;
    (2)多对多模型对用户态线程的数量也没有限制,在多处理器系统上,多对多模型的线程也能得到一定的性能提升,不过提升的幅度不如一对一模型高;

  • 相关阅读:
    结构体-寻找爱好相同的人
    什么是系统集成项目管理工程师,证书难考吗?
    Spring更简单的使用方法
    RestCloud ETL 跨库数据聚合运算
    Java中的异常以及异常处理
    融云:让银行轻松上“云”
    线程基础(一)
    RemObjects Elements 12.0 Crack
    SpringBoot整合Mybatis逆向工程
    【C++】类和对象 从入门到超神
  • 原文地址:https://blog.csdn.net/weixin_42031299/article/details/126962441