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

1、线程与进程的区别

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

2、线程的定义

linux线程详解:线程概念、线程调度、线程安全、线程模型_第1张图片

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

3、线程的访问权限

linux线程详解:线程概念、线程调度、线程安全、线程模型_第2张图片

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

4、使用多线程的好处

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

5、线程的调度

5.1、线程的状态

linux线程详解:线程概念、线程调度、线程安全、线程模型_第3张图片

状态 含义
运行态 线程正在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、线程同步的方法

linux线程详解:线程概念、线程调度、线程安全、线程模型_第4张图片

(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、一对一模型

linux线程详解:线程概念、线程调度、线程安全、线程模型_第5张图片

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

7.2、多对一模型

linux线程详解:线程概念、线程调度、线程安全、线程模型_第6张图片

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

7.3、多对多模型

linux线程详解:线程概念、线程调度、线程安全、线程模型_第7张图片

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

你可能感兴趣的:(#,《程序员的自我修养》,linux,线程,调度策略)