进程与线程

2019-07-14 10:03发布

一、进程:

进程:指在系统中能独立运行并作为资源分配的基本单位,它是由一组机器指令、数据和堆栈等组成的,是一个能独立运行的活动实体。
    由程序段、相关的数据段和PCB三部分构成了进程实体(又称进程印像),一般,我们把进程实体就简称为进程。

进程控制块(PCB):PCB不但可以记录进程的属性信息,以便操作系统对进程进行控制和管理,而且PCB标志着进程的存在,
操作系统根据系统中是否有该进程的进程控制块PCB而知道该进程存在与否。系统建立进程的同时就建立该进程的PCB,
在撤销一个进程时,也就撤销其PCB,故进程的PCB对进程来说是它存在的具体的物理标志和体现。
一般PCB包括以下信息:
(1)进程标识符(内部,外部)
(2)处理机的信息(通用寄存器,指令计数器,PSW,用户的栈指针)。
(3)进程调度信息(进程状态,进程的优先级,进程调度所需的其它信息,事件)
(4)进程控制信息(程序的数据的地址,资源清单,进程同步和通信机制,链接指针)

 注意:进程一般有三个状态:就绪状态、执行状态和等待状态【或称阻塞状态】;
 进程只能由父进程建立,系统中所有的进程形成一种进程树的层次体系;挂起命令可由进程自己和其他进程发出,但是解除挂起命令只能由其他进程发出。


进程的几个状态:
主要是3中基本状态,5状态和7状态可以直接看书 
1. 就绪状态:进程获得了除了CPU之外的所有的必要资源,只要获得CPU就可以立即执行,此时的进程处于就绪态 
2. 执行状态:进程已经获得CPU,正在运行,在多处理其系统中,会有多个进程同时处于运行状态 
3. 阻塞状态:处于执行状态的进程由于发生某些事件而暂时无法继续执行,放弃处理机而处于暂停状态,此时进程就处于阻塞(执行受到阻塞)状态


就绪->执行:调度进程为其分配了处理机 
执行->就绪:时间片用完 
执行->阻塞:申请临界资源而未被满足,如IO请求或者申请缓存 
阻塞->就绪:请求得到满足,如IO完成


进程之间的通信方式及优缺点


1)管道
管道分为有名管道和无名管道
无名管道:是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用.进程的亲缘关系一般指的是父子关系。
无明管道一般用于两个不同进程之间的通信。当一个进程创建了一个管道,并调用fork创建自己的一个子进程后,父进程关闭读管道端,
子进程关闭写管道端,这样提供了两个进程之间数据流动的一种方式。
有名管道:也是一种半双工的通信方式,但是它允许无亲缘关系进程间的通信。


无名管道:优点:简单方便;缺点:1)局限于单向通信2)只能创建在它的进程以及其有亲缘关系的进程之间;3)缓冲区有限;
有名管道:优点:可以实现任意关系的进程间的通信;缺点:1)长期存于系统中,使用不当容易出错;2)缓冲区有限
2)信号量
信号量是一个计数器,可以用来控制多个线程对共享资源的访问.,它不是用于交换大批数据,而用于多线程之间的同步.
它常作为一种锁机制,防止某进程在访问资源时其它进程也访问该资源.因此,主要作为进程间以及同一个进程内不同线程之间的同步手段.
优点:可以同步进程;缺点:信号量有限


3)信号
信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生.


4)消息队列
消息队列是消息的链表,存放在内核中并由消息队列标识符标识.消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等特点.
消息队列是UNIX下不同进程之间可实现共享资源的一种机制,UNIX允许不同进程将格式化的数据流以消息队列形式发送给任意进程.
对消息队列具有操作权限的进程都可以使用msget完成对消息队列的操作控制.通过使用消息类型,进程可以按任何顺序读信息,或为消息安排优先级顺序.


(消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。
消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。)


优点:可以实现任意进程间的通信,并通过系统调用函数来实现消息发送和接收之间的同步,无需考虑同步问题,方便;
缺点:信息的复制需要额外消耗CPU的时间,不适宜于信息量大或操作频繁的场合


5)共享内存
共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问.
共享内存是最快的IPC(进程间通信)方式,它是针对其它进程间通信方式运行效率低而专门设计的.
它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步与通信.


优点:无须复制,快捷,信息量大;缺点:1)通信是通过将共无法实现享空间缓冲区直接附加到进程的虚拟地址空间中来实现的,
因此进程间的读写操作的同步问题;2)利用内存缓冲区直接交换信息,内存的实体存在于计算机中,
只能同一个计算机系统中的诸多进程共享,不方便网络通信


6)套接字:套接口也是种进程间通信机制与其他通信机制不同的是,它可用于不同主机间的进程通信  
优点:1)传输数据为字节级,传输数据可自定义,数据量小效率高;2)传输数据时间短,性能高;3) 适合于客户端和服务器端之间信息实时交互;
4) 可以加密,数据安全性强
缺点:1) 需对传输的数据进行解析,转化成应用级的数据。


但是Socket是Linux的进程间通信,可以用于不同机器之间的进程


windows的进程间的通信方式有1.文件映射;2. 共享内存(是文件映射的一种特殊情况);3.邮件槽(mailslot)(点对点消息队列); 
4.匿名管道;5;命名管道; 6. 剪贴板;7.动态数据交换;8.对象链接与嵌入;9.远程过程调用;10.动态链接库;11.socket;12.WM_COPYDATA . 
linux进程间通信的方式有:1.管道 2.信号量 3.共享内存 4.消息队列 5.套接字 6.信号
windows和linux共有的进程间通信方式:1. 消息(linux中叫做信号) 2. 共享内存  3. 邮槽  4. 管道   5.socket


windows进程间同步方式
有:1. 互斥量 mutex 2. 信号量 semaphore 3.事件 event 4.临界区 Critical Section 5.互锁函数
临界区和互锁函数没有相应的内核对象因而不能跨进程


Linux进程同步方式有:互斥量、读写锁、条件变量
linux内核同步方法:原子操作、自旋锁、读-写自旋锁、信号量、读-写信号量、完成变量、BKL、禁止抢占(preemp_disable()、preemp_enable()

二、线程:


线程:线程是进程中的一个实体,作为系统调度和分派的基本单位。在多线程OS中,通常是在一个进程中包括多个线程,
每个线程都是作为利用CPU的基本单位,是花费最小开销的实体。一个标准的线程由线程ID,当前指令指针(PC),
寄存器集合和堆栈组成。
另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,
线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。
一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。
线程的实体包括程序、数据和TCB。线程是动态概念,它的动态特性由线程控制块TCB(Thread Control Block)描述。TCB包括以下信息:
(1)线程状态。
(2)当线程不运行时,被保存的现场资源。
(3)一组执行堆栈。
(4)存放每个线程的局部变量主存区。
(5)访问同一个进程中的主存和其它资源。
用于指示被执行指令序列的程序计数器、保留局部变量、少数状态参数和返回地址等的一组寄存器和堆栈。


线程之间的通信方式:

# 锁机制:包括互斥锁、条件变量、读写锁
   *互斥锁提供了以排他方式防止数据结构被并发修改的方法。
   *读写锁允许多个线程同时读共享数据,而对写操作是互斥的。
   *条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。
# 信号量机制(Semaphore):包括无名线程信号量和命名线程信号量
# 信号机制(Signal):类似进程间的信号处理
    线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。

线程同步的几种方法:
1、临界区(CCriticalSection)
2、时间(CEvent)
3、互斥量(CMutex)
4、 信号量(CSemphore)
具体见链接http://www.cnblogs.com/lebronjames/archive/2010/08/11/1797702.html

线程是否具有相同的堆栈? dll是否有独立的堆栈?

线程有自己的堆栈 
DLL的堆栈是在运行DLL的线程中的DLL本身是没有的
进程“没有自己的堆”,它使用的堆是进程地址空间的一部分,
不过,DLL能够在进程的地址空间中创建一个堆来供自己使用!dll没有自己的内存空间,它的空间依附于调用它的进程
线程有自己独立的stack
dll没有stack,使用它所attach的线程的stack

c++中同Java中sleep()和wait()有什么区别?
用什么关键字修饰同步方法 ? 用synchronized关键字修饰同步方法
同步有几种实现方法,都是什么?分别是synchronized,wait与notify
wait():使一个线程处于等待状态,并且释放所持有的对象的lock。
sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。
notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。
Allnotity():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。
实现同步的方式
同步是多线程中的重要概念。同步的使用可以保证在多线程运行的环境中,程序不会产生设计之外的错误结果。同步的实现方式有两种,
同步方法和同步块,这两种方式都要用到synchronized关键字。


进程与线程的区别
(1)调度:
在传统的操作系统中,CPU调度和分派的基本单位是进程。而在引入线程的操作系统中,则把线程作为CPU调度和分派的基本单位,
进程则作为资源拥有的基本单位,从而使传统进程的两个属性分开,线程编程轻装运行,这样可以显著地提高系统的并发性。
同一进程中线程的切换不会引起进程切换,从而避免了昂贵的系统调用,但是在由一个进程中的线程切换到另一进程中的线程,依然会引起进程切换。
 
(2)并发性:
  在引入线程的操作系统中,不仅进程之间可以并发执行,而且在一个进程中的多个线程之间也可以并发执行,
  因而使操作系统具有更好的并发性,从而更有效地提高系统资源和系统的吞吐量。例如,
  在一个为引入线程的单CPU操作系统中,若仅设置一个文件服务进程,当它由于某种原因被封锁时,
  便没有其他的文件服务进程来提供服务。在引入线程的操作系统中,可以在一个文件服务进程设置多个服务线程。
  当第一个线程等待时,文件服务进程中的第二个线程可以继续运行;当第二个线程封锁时,第三个线程可以继续执行,
  从而显著地提高了文件服务的质量以及系统的吞吐量。

(3)拥有资源:
  不论是引入了线程的操作系统,还是传统的操作系统,进程都是拥有系统资源的一个独立单位,
  他可以拥有自己的资源。一般地说,线程自己不能拥有资源(也有一点必不可少的资源),
  但它可以访问其隶属进程的资源,亦即一个进程的代码段、数据段以及系统资源(如已打开的文件、I/O设备等),
  可供同一个进程的其他所有线程共享。

(4)独立性:
在同一进程中的不同线程之间的独立性要比不同进程之间的独立性低得多。这是因为
为防止进程之间彼此干扰和破坏,每个进程都拥有一个独立的地址空间和其它资源,除了共享全局变量外,
不允许其它进程的访问。但是同一进程中的不同线程往往是为了提高并发性以及进行相互之间的合作而创建的,
它们共享进程的内存地址空间和资源,如每个线程都可以访问它们所属进程地址空间中的所有地址,如一个线程的堆栈可以被其它线程读、写,甚至完全清除。
(5)系统开销:
       由于在创建或撤销进程时,系统都要为之分配或回收资源,如内存空间、I/O设备等。因此,
  操作系统为此所付出的开销将显著地大于在创建或撤消线程时的开销。类似的,在进程切换时,
  涉及到整个当前进程CPU环境的保存环境的设置以及新被调度运行的CPU环境的设置,
  而线程切换只需保存和设置少量的寄存器的内容,并不涉及存储器管理方面的操作,可见,
  进程切换的开销也远大于线程切换的开销。此外,由于同一进程中的多个线程具有相同的地址空间,
  致使他们之间的同步和通信的实现也变得比较容易。在有的系统中,现成的切换、同步、和通信都无需操作系统内核的干预。

(6)支持多处理机系统:
       在多处理机系统中,对于传统的进程,即单线程进程,不管有多少处理机,该进程只能运行在一个处理机上。
  但对于多线程进程,就可以将一个进程中的多个线程分配到多个处理机上,使它们并行执行,这无疑将加速进程的完成。
  因此,现代处理机OS都无一例外地引入了多线程。


进程与线程的关系


一个进程中的所有线程共享该进程的地址空间,但它们有各自独立的(私有的)栈(stack),Windows线程的缺省堆栈大小为1M。


堆:是大家共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。
堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是记得用完了要还给操作系统,要不然就是内存泄漏。


栈:是个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立,
因此,栈是 thread safe的。操作系统在切换线程的时候会自动的切换栈


线程共享内容、线程独享内容 将线程共有的信息存放在进程控制块中,将线程独有的信息存放在线程控制块中;
那么如何区分哪些信息是共享的?哪些信息是独享的呢?
一般的评价标准是:如果某些资源不独享会导致线程运行错误,则该资源就由每个线程独享,
而其他资源都由进程里面的所有线程共享。
1.线程共享内容:
进程代码段
进程的公有数据(利用这些共享的数据,线程很容易的实现相互之间的通讯)、
进程打开的文件描述符、
信号的处理器、信号及信号服务器
进程的当前目录和
进程用户ID与进程组ID
地址空间
全局变量
子进程
闹铃
记账信息


2.线程独享内容
线程ID
寄存器组的值
线程的堆栈
错误返回码
线程的信号屏蔽码
程序计数器
状态字


进程与线程的实现——进程由操作系统内核实现,线程可以分为内核态线程和用户态线程
1.进程的实现
首先应该明白进程的调度,创建等实质上都是由操作系统实现的,所以说进程的实现只能由操作系统内核来实现,
而不存在用户态实现的情况。


2.线程的实现
对于线程就不同了,线程的管理者可以是用户也可以是操作系统本身,线程是进程内部的东西,
当然存在由进程直接管理线程的可能性。因此线程的实现就应该分为内核态线程实现和用户态线程实现。


(1)内核态线程实现:
线程是进程的不同执行序列,也就是说线程是独立运行的基本单位,也是CPU调度的基本单位。
首先操作系统向管理进程一样,应该保持维护线程的所有资源,将线程控制块存放在操作系统的内核空间中。
那么此时操作系统就同时掌管进程控制块和线程控制块。
操作系统管理线程优点:
a 用户编程简单;
b 如果一个线程执行阻塞操作,操作系统可以从容的调度另外一个线程的执
行。

内核线程缺点:
a 效率低,因为线程在内核态实现,每次线程切换都需要陷入到内核,由操作系统来调度,而有用户态切换到内核态是要话费很多时间的,
 另外内核态实现会占用内核稀有的资源,因为操作系统要维护线程列表,操作系统所占内核空间一旦装载后就无法动态改变,
 并且线程的数量远远大于进程的数量,随着线程数的增加内核将耗尽;
b 内核态的实现需要修改操作系统,这个是谁都不想要做的事情;

(2)用户态线程:
用户态管理线程就是用户自己做线程的切换,自己管理线程的信息,操作系统无需知道线程的存在。
在用户态下进行线程的管理需要用户创建一个调度线程。一个线程在执行完一段时间后主动把资源释放给其他线程使用,
而在内核台下则无需如此,因为操作系统可通过周期性的时钟中断把控制权夺过来,在用户态实现情况下,
执行系统的调度器也是线程,没有能力夺取控制权。

用户态线程优点:
首先是灵活,因为操作系统不用知道线程的存在,所以任何操作系统上都能应用;
其次,线程切换快,因为切换在用户态进行,无需陷入带内核态;
再次,不用修改操作系统实现容易。

用户态线程缺点:
首先编程起来很诡异,由于在用户台下各个进程间需要相互合作才能正常运转。那么在编程时必须考虑什么情况下让出CPU,
让其他的线程运行,而让出时机的选择对线程的效率和可靠性有很大影响,这个并不容易做到;

       其次,用户态线程实现无法完全达到线程提出所要达到的目的:进程级多道编程;如果在执行过程中一个线程受阻,
  它将无法将控制权交出来,这样整个进程都无法推进。
操作系统随即把CPU控制权交给另外一个进程。这样,
  一个线程受阻造成整个进程受阻,我们期望的通过线程对进程实施分身的计划就失败了。这是用户态线程致命的缺点。
  
       调度器激活:线程阻塞后,CPU控制权交给了操作系统,要激活受阻进程的线程,唯一的办法就是让操作系统在进程切换时先不切换,
  而是通知受阻的进程执行系统(即调用执行系统),并问其是否还有别的线程可以执行。如果有,
  将CPU控制权交给该受阻进程的执行系统线程,从而调度另一个可以执行的线程到CPU上。一个进程挂起后,
  操作系统并不立即切换到别的进程上,而是给该进程二次机会,让其继续执行。如果该进程只有一个线程,
  或者其所有线程都已经阻塞,则控制权将再次返回给操作系统。而现在,操作系统就会切换到其他线程了。
  
  
现在操作系统的线程实现模型:
鉴于用户态与内核态都存在缺陷,现代操作将两者结合起来。用户态的执行负责进程内部线程在非阻塞时的切换;
内核态的操作系统负责阻塞线程的切换,即我们同时实现内核态和用户态线程管理。每个内核态线程可以服务一个或者更多个用户态线程。


线程从用户态切换到内核态:
什么情况下会造成线程从用户态到内核态的切换呢?
首先,如果在程序运行过程中发生中断或者异常,系统将自动切换到内核态来运行中断或异常处理机制。
此外,程序进行系统调用也会从用户态切换到内核态。

热门文章