进程线程和协程的特性和区别

进程

进程是操作系统分配资源的最小单位

​ 一个进程中有多个线程,一个线程里面可以有多个协程,因为进程是最大的了,进程切换需要的开销很大,但是进程挂了,其他进程因为保护机制会不受影响,但是这个进程里面的线程协程也都会挂掉。

进程间通信的方式

1)管道(pipe)及有名管道(named pipe):

  管道可用于具有亲缘关系的父子进程间的通信;有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。

  2)信号(signal):

  信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一致的。

  3)消息队列(message queue):

  消息队列是消息的链接表,它克服了上两种通信方式中信号量有限的缺点,具有写权限得进程可以按照一定得规则向消息队列中添加新信息;对消息队列有读权限得进程则可以从消息队列中读取信息。

  4)共享内存(shared memory):

  可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。

  5)信号量(semaphore):

  主要作为进程之间及同一种进程的不同线程之间得同步和互斥手段。

  6)套接字(socket);

  这是一种更为一般得进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛。

线程

线程是操作系统所能调度执行的最小单位

计算机的cpu物理核数是同时可以并行的线程数量(cpu只能看到线程,线程是cpu调度分配的最小单位

线程间的通信方式

1)锁机制:包括互斥锁、条件变量、读写锁

    a. 互斥锁提供了以排他方式防止数据结构被并发修改的方法。

    b. 读写锁允许多个线程同时读共享数据,而对写操作是互斥的。

    c. 条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。

  2)信号量机制(Semaphore):包括无名线程信号量和命名线程信号量

  3)信号机制(Signal):类似进程间的信号处理

线程的几种状态

**1、新建状态(New)**:新创建了一个线程对象。

**2、就绪状态(Runnable)**:线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取CPU的使用权,

即在就绪状态的进程除CPU之外,其它的运行所需资源都已全部获得。

**3、运行状态(Running)**:就绪状态的线程获取了CPU,执行程序代码。

**4、阻塞状态(Blocked)**:阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。

阻塞的情况分三种:

①.等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,

必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒,

②.同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。

③.其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时,

或者I/O处理完毕时,线程重新转入就绪状态。

**5、死亡状态(Dead)**:线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

什么是线程死锁?

概念:线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行

死锁产生的原因

  • 系统资源不足
  • 进程推进的顺序不合适
  • 资源分配不当

产生死锁的条件

  • 互斥条件:进程对于所分配到的资源具有排它性,即一个资源只能被一个进程占用,直到被该进程释放
  • 请求和保持条件:一个进程因请求被占用资源而发生阻塞时,对已获得的资源保持不放。
  • 不剥夺条件:任何一个资源在没被该进程释放之前,任何其他进程都无法对他剥夺占用
  • 循环等待条件:当发生死锁时,所等待的进程必定会形成一个环路(类似于死循环),造成永久阻塞。

避免死锁的办法:银行家算法

假设资源P1申请资源,银行家算法先试探的分配给它(当然先要看看当前资源池中的资源数量够不够),若申请的资源数量小于等于Available,然后接着判断分配给P1后剩余的资源,能不能使进程队列的某个进程执行完毕,若没有进程可执行完毕,则系统处于不安全状态(即此时没有一个进程能够完成并释放资源,随时间推移,系统终将处于死锁状态)。

若有进程可执行完毕,则假设回收已分配给它的资源(剩余资源数量增加),把这个进程标记为可完成,并继续判断队列中的其它进程,若所有进程都可执行完毕,则系统处于安全状态,并根据可完成进程的分配顺序生成安全序列(如{P0,P3,P2,P1}表示将申请后的剩余资源Work先分配给P0–>回收(Work+已分配给P0的A0=Work)–>分配给P3–>回收(Work+A3=Work)–>分配给P2–>······满足所有进程)。

处理死锁的方法

破坏产生死锁的必要条件,增加系统资源,改变合理的进程推进顺序,改善资源分配方式

协程

协程是用户态的轻量级线程,操作系统所不能开辟的,但是可以由程序控制

协程主要是为了提高并发,而且主要是IO并发。协程并不适合并行计算或者并行处理任务,因为同一时刻运行的协程数不可能大于操作系统线程。

协程的优势

1、极高的执行效率,因为子程序切换不是线程切换,而是程序自身切换,没有线程切换开销效率高。

2、不需要多线程的锁机制,因为只有一个线程不存在同时写变量冲突。

为什么协程切换代价比线程低

协程切换流程:

1、保存当前寄存器信息

2、选择下一个要执行的协程

3、恢复寄存器上下文

线程切换流程:

1、软中断

2、保存寄存器信息到内核堆栈、

3、选择下一个要执行的线程

4、恢复上下文

5、返回用户堆栈

  • 需要保存的寄存器信息大小不同,线程需要把寄存器保存到内核堆栈,并且 返回用户堆栈

  • 协程无多线程锁机制,因为一个协程运行就一个线程。

  • 协程,是一种比线程更加轻量级的存在,协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行)。这样带来的好处就是性能得到了很大的提升,不会像线程切换那样消耗资源。

协程在子程序内部是可中断的,然后转而执行别的子程序,在适当的时候再返回来接着执行

协程的特点在于是一个线程执行

极高的执行效率:因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显;

不需要多线程的锁机制:因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

问题:为什么要把线程的上下文信息保存到内核态中

因为用户态的权限比较低,不能直接访问硬盘内存等资源,只能通过系统调用来做。

go语言内的协程

goroutine 非常的轻量,初始分配只有 2KB,当栈空间不够用时,会自动扩容。同时,自身存储了执行 stack 信息,用于在调度时能恢复上下文信息。

而线程比较重,一般初始大小有几 MB(不同系统分配不同),线程是由操作系统调度,是操作系统的调度基本单位。而 golang 实现了自己的调度机制,goroutine 是它的调度基本单位。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!