操作系统中线程的实现模型

转自https://blog.csdn.net/fuzhongmin05/article/details/55802984

1、为什么需要线程?

首先需要回答一个问题,为什么操作系统需要线程。如果非要说是为什么需要线程,还不如说为什么需要进程中还有其它进程。这些进程中包含的其它迷你进程就是线程。进程有以下缺陷:

1、进程只能在一个时间内干一件事(执行一个程序执行流),而如果想同时干两件或多件事情,进程就不够用了。
2、进程在执行过程中如果阻塞,例如等待输入,整个进程就将挂起,而无法继续执行。这样,即使进程里面有部分工作不依赖于输入数据,也无法推进。
为了解决这个问题,人们发明了线程。线程之所以说是迷你进程,是因为线程和进程有很多相似之处,比如线程和进程的状态都有运行,就绪,阻塞状态。这几种状态理解起来非常简单,当进程所需的资源没有到位时会是阻塞状态,当进程所需的资源到位但CPU没有到位时是就绪状态,当进程既有所需的资源,又有CPU时,就为运行状态。使用多线程,每个线程仅仅需要处理自己那一部分应该完成的任务,而不用去关心和其它线程的冲突。因此简化了编程模型。
更具体的说,线程的好处如下:

1、在很多程序中,需要多个线程互相同步或互斥的并行完成工作,而将这些工作分解到不同的线程中去无疑简化了编程模型。
2、因为线程相比进程来说,更加的轻量,故线程的创建和销毁的代价变得更小。
2、线程提高了性能,虽然线程宏观上是并行的,但微观上却是串行(单CPU或单核时)。从CPU角度线程并无法提升性能,但如果某些线程涉及到等待资源(比如IO,等待输入)时,多线程允许进程中的其它线程继续执行而不是整个进程被阻塞,因此提高了CPU的利用率,从这个角度会提升性能。
3、在多CPU或多核的情况下,使用线程不仅仅在宏观上并行,在微观上也是并行的。
进程有自己独立的内存地址空间,而线程共享进程的内存地址空间。

2、经典的线程模型

另一个看进程和线程的角度是进程模型基于两类不同的概念:资源的组织和执行。在过去没有线程的操作系统中,资源的组织和执行都是由进程完成的。但这两者很多时候需要加以区分,这也是为什么需要引入线程。进程是用于组织资源的单位,进程将相关的资源组织在一起,这些资源包括:内存地址空间,程序,数据等,将这些以进程的形式组织起来可以使得操作系统管理这些资源更为容易。而线程,是每一个进程中执行的一个条线。线程虽然共享进程中的大多数资源,但线程也需要自己的一些资源,比如:用于标识下一条执行指令的程序计数器,一些容纳局部变量的寄存器,以及用于表示执行的历史的栈。

总而言之:进程是组织资源的最小单位,而线程是安排CPU执行的最小单位。其实在一个进程中多个线程并行和在操作系统中多个进程并行非常类似,只是线程共享的是地址空间,而进程共享的是物理内存,打印机,键盘等资源。每一个进程和线程所独自占有的资源如表1所示。

表1

进程占有的资源 线程占有的资源
地址空间
全局变量 寄存器
打开的文件 程序计数器
信号量 状态
账户信息  

其中,线程可以共享进程独占的资源。我们常用的术语“多线程”一般指的是在同一个进程中多个线程的并发执行。线程是进程里面的一个执行上下文,或者执行序列。同一个地址空间里面的所有线程就构成了进程。线程是CPU切换的最小单位,进程是资源分配的最小单位。

例如:当我们使用Word时,实际上是打开了多个线程。这些线程一个负责显示,一个接受输入,一个定时存盘。这些线程一起运转(同时参与竞争CPU),让我们感觉到输入和屏幕显示同时发生,而不用键入一些字符,等待一会儿才看到屏幕显示。

3、线程管理与实现方式

要管理线程就要维持线程的各种信息,存放这些信息的数据结构称为线程控制块。线程共享一个进程空间,因此许多资源是共享的(如地址空间、全局变量、文件等),这些共享资源存放在进程控制块即可。还有一些不被共享的资源和信息(如程序计数器、寄存器等),需要存放在线程控制块里。 由谁来管理线程有两种选择:一是让进程自己来管理线程;二是让操作系统来管理线程。由进程自己管理就是用户态线程实现,由操作系统管理就是内核态线程实现。

3.1 线程实现在用户空间下

当线程在用户空间下实现时,操作系统对线程的存在一无所知,操作系统只能看到进程,而不能看到线程。所有的线程都是在用户空间下实现。在操作系统看来,每一个进程只有一个线程。过去的操作系统大部分是这种实现方式,这种方式的好处之一就是即使操作系统不支持线程,也可以通过库函数来支持线程。在这种模式下,每一个进程中都维护着一个线程表来追踪本进程中的线程,这个表中包含表1中每个线程独占的资源,比如栈,寄存器,状态等,如图1所示。

图1 线程实现在用户空间下

这种模式当一个线程完成了其工作或等待需要被阻塞时,其调用系统过程阻塞自身,然后将CPU交由其它线程。用户线程多见于一些历史悠久的操作系统,如UNIX操作系统。这种的模式的好处,首先,是在用户空间下进行进程切换的速度要远快于在操作系统内核中实现。其次,在用户空间下实现线程使得程序员可以实现自己的线程调度算法。比如进程可以实现垃圾回收器来回收线程。还有,当线程数量过多时,由于在用户空间维护线程表,不会占用大量的操作系统空间。最后是灵活性,由于操作系统不需要知道线程的存在,所以在任何操作系统上都能应用。

这种模式最致命的缺点也是由于操作系统不知道线程的存在,因此当一个进程中的某一个线程进行系统调用时,比如缺页中断而导致线程阻塞,此时操作系统会阻塞整个进程,即使这个进程中其它线程还在工作。此外会造成编程困难,我们在写程序的时候必须仔细斟酌在什么时候应该让出CPU给别的线程使用。还有一个问题是假如进程中一个线程长时间不释放CPU,因为用户空间并没有时钟中断机制,会导致此进程中的其它线程得不到CPU而持续等待。

3.2 线程实现在操作系统内核中

在这种模式下,操作系统知道线程的存在。此时线程表存在操作系统内核中,如图2所示。线程应该是CPU调度的基本单位,操作系统要管理线程,就要保持维护线程的各种资料,即将线程控制块存放在操作系统内核空间。这样操作系统内核就同时保有进程控制块和线程控制块。

图2 线程实现在操作系统内核空间中

在这种模式下,所有可能阻塞线程的调用都以系统调用(System Call)的方式实现,相比在用户空间下实现线程造成阻塞的运行时调用(System runtime call)成本会高出很多。当一个线程阻塞时,操作系统可以选择将CPU交给同一进程中的其它线程,或是其它进程中的线程,而在用户空间下实现线程时,调度只能在本进程中执行,直到操作系统剥夺了当前进程的CPU。

因为在内核模式下实现进程的成本更高,一个比较好的做法是将线程回收利用,当一个线程需要被销毁时,仅仅是修改标记位,而不是直接销毁其内容,当一个新的线程需要被创建时,也同样修改被“销毁”的线程其标记位即可。名为内核态线程实现的优点是用户编程简单,用户程序员在编程的时候无需关心线程的调度,即无需担心线程什么时候会执行,什么时候会挂起。如果一个线程执行阻塞操作,操作系统可以从容地调度另外一个线程执行。因为操作系统能监控所有线程。

这种模式下同样还是有一些弊端,例如效率较低。因为线程在内核态实现,每次线程切换都需要陷入到内核,由操作系统来进行调度。其次,占用内核稀缺的内存资源。操作系统需要维护线程表,操作系统所占的内存空间一旦装载结束后就已经固定,无法动态改变。由于线程的数量大大高于进程的数量,那么随着线程数量的增加,操作系统内核空间将迅速耗尽。

3.3 现代操作系统的线程实现模型—混合模式

鉴于用户态和内核态都存在缺陷,现代操作系统使用的是将二者结合起来。用户态的执行系统负责进程内部线程在非阻塞时的切换;内核态的操作系统负责阻塞线程的切换,即我们同时实现内核态和用户态线程管理。其中内核态线程数量较少,而用户态线程数量多。每个内核态线程可以服务一个或多个用户态线程。换句话说,用户态线程被多路复用到内核态线程上。混合模式下,用户空间中的进程管理自己的线程,操作系统内核中有一部分内核级别的线程,如图3所示。

例如,某个进程有5个线程,我们可以将5个线程分成两组,一组3个线程,另一组2个线程。每一组线程使用一个内核线程。这样,该进程将使用两个内核线程。如果一个线程阻塞,则与其同属于一组的线程皆阻塞,但另外一组线程却可以继续执行。在这种模式下,在分配线程时,我们可将需要执行阻塞操作的线程设为内核态线程,而不会执行阻塞操作的线程设为用户态线程。这样我们就可以获得两种态势实现下的优点,而避免其缺点。

图3 线程实现的混合模型

在这种模式下,操作系统只能看到内核线程。用户空间线程基于操作系统线程运行。因此,程序员可以决定使用多少用户空间线程以及操作系统线程,这无疑具有更大的灵活性。而用户空间线程的调度和前面所说的在用户空间下执行实现线程是一样的,同样可以自定义实现。

原文地址:https://www.cnblogs.com/CloudStrife/p/9029583.html

时间: 2024-10-05 01:35:40

操作系统中线程的实现模型的相关文章

Java中线程和进程的区别是什么呢?

概念部分:1.进程是系统资源分配的最小单位,线程是CPU执行调度的最小单位(资源调度的最小单位) 2.进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段.堆栈段和数据段,这种操作非常昂贵.而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多. 3.线程之间的通信更方便,同一进程下的线程共享全局变量.静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行.不过如何处理好同

9 异常处理 操作系统 进程线程 队列+生产消费者模型 进程同步 回调函数

异常处理 异常就是程序运行时发生错误的信号,在python中,错误触发的异常如下 异常的种类: AttributeError 试图访问一个对象没有的树形,比如foo.x,但是foo没有属性x IOError 输入/输出异常:基本上是无法打开文件 ImportError 无法引入模块或包:基本上是路径问题或名称错误 IndentationError 语法错误(的子类) :代码没有正确对齐 IndexError 下标索引超出序列边界,比如当x只有三个元素,却试图访问x[5] KeyError 试图访

操作系统中的进程/线程互斥

在操作系统中,不同的进程和线程之间涉及到一个重要的问题就是互斥,即保证对共享数据的正确修改. 基本的思想就是避免多余一个进程或线程(后面统一用进程来代替)同时读写共享数据. 为了解决这个问题,有很多的想法,为了方便说明各自的问题,按照<现代操作系统>中的思路来叙述. 首先是想到的控制进程的执行顺序,提出了两个方法:一个是严格轮转法,一个是Peterson算法. 严格轮转法就是让进程挨个按序执行,进程0做完了让给进程1,依次下去:缺点就是有忙等待,而且进程0可能会被不在临界区的进程1阻塞(因为两

操作系统中的进程与线程

操作系统中的进程与线程 转自:http://www.cnblogs.com/CareySon/archive/2012/05/04/ProcessAndThread.html 简介 在传统的操作系统中,进程拥有独立的内存地址空间和一个用于控制的线程.但是,现在的情况更多的情况下要求在同一地址空间下拥有多个线程并发执行.因此线程被引入操作系统. 为什么需要线程? 如果非要说是为什么需要线程,还不如说为什么需要进程中还有其它进程.这些进程中包含的其它迷你进程就是线程. 线程之所以说是迷你进程,是因为

WINDOWS操作系统中可以允许最大的线程数(线程栈预留1M空间)(56篇Windows博客值得一看)

WINDOWS操作系统中可以允许最大的线程数 默认情况下,一个线程的栈要预留1M的内存空间 而一个进程中可用的内存空间只有2G,所以理论上一个进程中最多可以开2048个线程 但是内存当然不可能完全拿来作线程的栈,所以实际数目要比这个值要小. 你也可以通过连接时修改默认栈大小,将其改的比较小,这样就可以多开一些线程. 如将默认栈的大小改成512K,这样理论上最多就可以开4096个线程. 即使物理内存再大,一个进程中可以起的线程总要受到2GB这个内存空间的限制. 比方说你的机器装了64GB物理内存,

操作系统中的进程和线程

进程与进程的作用 当我们双击程序图标,开始运行程序时,就产生了一个进程.所以进程的本质是一个正在执行的程序.进程包含了程序运行的所需要的所有信息,如代码段,数据段,程序计数器(存放下一条指令所在的地址),进程标识符(PID)进程控制块(PCB,用来保存进程退出CPU时的现场信息)等等信息. 所以进程可以看做是容纳程序运行的所有信息的容器. 值得注意的是,一个程序如果运行了两遍,则算是两个进程.如运行了两个word程序,这两个进程除了代码段,其他信息都是不一样的,事实上,这两个进程共享代码段. 进

boost中asio网络库多线程并发处理实现,以及asio在多线程模型中线程的调度情况和线程安全。

1.实现多线程方法: 其实就是多个线程同时调用io_service::run for (int i = 0; i != m_nThreads; ++i)        {            boost::shared_ptr<boost::thread> pTh(new boost::thread(                boost::bind(&boost::asio::io_service::run,&m_ioService)));            m_l

操作系统中作业、线程、进程、内存管理、垃圾回收以及缓存等概念

作业:用户在一次解题或是一个事务处理过程中要求计算机系统所做的工作的集合.它包括用户程序.所需要处理的数据以及控制命令等.作业是由一系列有序的步骤组成. 进程:一个程序在一个数据集合的一次运行过程.所以一个程序在不同数据集合上运行,乃至一个程序在同样的数据集合上的多次运行都是不同的进程. 线程:线程是进程中的一个实体,被系统独立调度和执行的基本单位. 管程:管程实际上是定义了一个数据结构和在该数据结构上的能为并发进程做执行的一组操作,这组操作能同步进程和改变管程中的数据. 操作系统中作业.线程.

生产者消费者模型中线程怎样正常退出

生产者:不停地往队列中放数据 消费者:不停地从队列中拿数据 两者通过两个信号量同步 当生产者不再生产数据时,消费者正好挂在一个信号量上,处于睡眠状态,这时候pthread_join也会一直挂着的.该怎样使得消费者正常退出呢? 我的做法是让生产者在往队列中放一个[结束数据],也就是一个标识,消费者拿到数据后,如果这个数据是结束标识则自杀退出. 生产者消费者模型中线程怎样正常退出