并发之线程、进程、纤程、协程、管程与死锁,活锁

1、进程【Progress】

      定义:操作系统中能够独立运行的个体,资源分配的基本单位。由指令、数据、堆栈等结构组成。

      生命周期:

  1. 程序安装好后,未运行之前,仅是一些文件存储在磁盘上;
  2. 程序启动时,向OS【Operating System 操作系统】申请一定资源,如CPU、存储空间和I/O设备等;
  3. OS为其分配资源后,才会真正出现内存中的抽象概念:进程。

     后沿:进程的调度开销很大。并发中切换过程效率很低,为了更高效的调度和满足复杂程序的需求,有了线程。

2、线程【Thread】

       定义:

  1.  程序执行最小单元;
  2. 一个进程可以拥有多条线程,所有线程可以共享进程的内存区域;
  3. 线程通常运行时也需要一组寄存器、内存、栈等资源支持。

       如果机器为八核CPU,理论同一时刻最多支持8条内核线程同时并发,如果采用超线程技术,把1个物理芯片模拟成两个逻辑处理核心,比如四核八线程的CPU,同一时间也支持最大8条线程并发执行。

       在OS中,程序一般不会直接申请内核线程进行操作,而是去使用内核线程提供的一种名为LWP【Lightweight Process】的轻量级进程进行操作,这个也就是平时所谓的线程,也被称为用户级线程。

       2.1、线程模型

               用户线程与内核线程主要存在3种模型:一对一模型、多对一模型和多对多模型。

               2.1.1、一对一模型

                          定义:一条用户线程对应内核中的一条线程。比如java

                          

 

 

            一对一模型是真正意义上的并行执行,因为这种模型下,创建一条java的Thread线程是真正的在内核中创建并映射了一条内核线程的,执行过程中,一条线程不会因为宁外一条线程的原因而发生阻塞等情况。不过因为是直接映射内核线程模式,所以数量会存在上限。并且同一个核心中,多条线程的执行需要频繁的发生上下文切换以及内核态与用户态之间切换,所以如果线程数量过多,切换过于平凡会导致线程执行效率下降。

            2.1.2、多对一模型

                       指多条用户线程映射一条内核线程的情况,对于用户线程而言,他们的执行都是由用户态的代码来完成切换的。

                        

 

 

                   优点:

  1. 可以节省内核态到用户态的切换;
  2. 线程的数量不会受到内核线程的限制。

                   缺点:

  1. 因为线程切换的工作事由用户态的代码来完成,所有如果当一条线程发生阻塞时,与该内核对应的其他用户线程也会一起陷入阻塞。

                  2.1.3、多对多模型

                             

 

 

 

                         优点:

  1. 避免一对一和多对多带来的弊端,也就是多条用户线程映射多条内核线程,可以避免一对一模型的切换效率问题和数量限制问题,也可以避免多对一的阻塞问题。

3、协程【Coroutines】

          协程是一种基于线程之上,但比线程更加轻量级的存在。由程序管理的轻量级线程也被称为用户空间线程,对内核而言是不可见的。正如同进程中存在多条线程一样,线程中也可以存在多个协程。

          协程在运行时也有自己的寄存器,上下文和栈,协程的调度完全由用户控制,协程调度切换时,会将寄存器上下文和栈保存到分配的私有内存区域中,在切换回来的时候,回复先前保存的寄存器上下文和栈,直接操作栈,则基本没有内核开销,可以不加锁的访问全局变量,所以上下文切换的非常快。

         协程有些类似线程多对一模型。

4、纤程【Fiber】

      纤程是Microsoft组织为了帮助企业程序更好移植到Windows系统,而在操作系统中增加的一个概念,由操作系统内核根据对应的调度算法进行控制,也是一种轻量级的线程。

     纤程和协程的概念一致,都是线程的多对一模型,但是有些地方会区分开来,但从协程的本质概念上来谈:纤程、绿色线程、微线程这些概念都是属于协程范畴。

    纤程和协程的区别在于:纤程是OS级别的实现,而协程是语言级别的实现,纤程被OS控制,协程对于内核而言是不可见的。

5、管程【Monitors】

     管程提供一种机制,线程可以临时放弃互斥访问,等待某些条件得到满足后,重新获得执行权回复它的互斥访问。

6、总结

  1. 从实现级别上来讲:进程、线程、纤程是OS级别的实现,而绿色线程、协程这些则是语言级别上的实现。
  2. 从调度方式而言:进程、线程、绿色线程属于抢占式执行,而纤程、协程则属于合作式调度。
  3. 从包含关系来说:一个OS中可以有多个进程,一个进程中可以有多条线程,而一个线程中则可以有多个协程、纤程、微线程等。

7、死锁、活锁、锁饥饿

     多核时代,多线程,多进程提高了资源利用率和系统吞吐量,也带来了一系列问题:死锁、活锁、锁饥饿。

     7.1、死锁【DeadLock】

              定义:两个或者两个以上的线程(或进程)在运行过程中,因为资源竞争而导致相互等待的现象,若无外力作用,则不会解除等待现象,他们之间的执行都将无法继续下去。

     7.2、活锁【LiveLock】

              定义:正在执行线程或者进程,没有发生阻塞,但由于某些条件没有满足,导致反复重试-失败-重试-失败的过程。

              区别:与死锁最大的区别在于:活锁状态的线程或进程是一致处于运行状态的,在失败中不断重试,重试中不断失败,一直处于所谓的“活”态,不会停止。而发生死锁的线程则是相互等待,双方之间的状态是不会发生改变的,处于所谓的“死”态。

                        死锁没有外力介入是无法自行解除的,而活锁状态有一定几率自行解除。

                        除多线程之间协调导致的活锁情况外,单线程也会导致活锁。比如RPC调用,A调用B的RPC接口,需要B返回数据,结果B所在机器网络异常,A就不断的重试,导致反复调用,不断失败。

              解决方法:活锁是有可能自行解除的,但时间可能会久一点,在编写程序时,尽量避免活锁情况发生,一方面可以在重试次数添加限制,另一方面可以把重试时间间隔加点随机数,还有就是多线程协同工作时,则可以在全局内约定好重试机制,避免线程冲突。

     7.3、锁饥饿【LockStarving】

              定义:一条长时间等待的线程无法获取到锁资源或执行所需资源,而后面来的新线程反而“插队”先获取了资源执行,最终导致这条长时间等待的线程出现饥饿。

              举例:

                     1、ReetrantLock的非公平锁就有可能导致线程饥饿问题,因为线程到来的先后顺序无法决定锁的获取,可能第二条到来的线程在第十八条线程获取锁成功后,也不一定能成功获取锁。

                     2、使用多线程编程,但是分配纤程组没有合理的设置优先级,导致高优先级线程一直吞噬低优先级资源,导致低优先级线程一直无法获取到资源执行,也会使低优先级线程产生饥饿。

              解决办法:可以采用公平锁的办法,确保线程获取锁的顺序是按照请求锁的先后顺序进行的,但是实际开发过程中,基于性能而言,非公平锁远超公平锁,吞吐量也比公平锁更高。

  8、死锁处理

        8.1、死锁产生的4个条件

  1.  互斥条件:指分配到的资源具有排他使用性,即在一段时间内,某资源只能有一个执行实体使用。如果此时还有其他执行实体请求资源,则请求者只能等待,直至占有的资源的执行实体使用完成后释放才行。
  2. 不可剥夺条件:指执行实体已有的资源,在未使用完全之前,不能被剥夺,只能在使用完成时由自己释放。
  3. 请求与保持条件:指运行过程中,执行实体已经获得至少一个资源,但又提出了新的资源请求,而该资源已被其他实体占用,此时当前请求资源实体阻塞,但在阻塞时却不释放自己已获得的其他资源,一直保持着对其他资源的占用。
  4. 环装等待条件:指在发生死锁时,必然存在一个执行实体的资源环装链。比如线程T1等待T2占用的一个资源,线程T2在等待T3占用的一个资源,而线程T3则在等待线程T1占用的一个资源,最终形成一个环装的资源等待链。

      以上是死锁发生的四个必要条件,只要系统或程序内发生死锁情况,那么这四个条件必然成立,只要上述中任意一条不符合,就不会发生死锁。

      8.2、系统资源分类

               OS上的资源分为:永久性资源、临时性资源、可抢占式资源、不可抢占式资源。

               【永久性资源】:也叫可重复性资源,代表一个资源可以被执行实体(线程/进程)重复性使用,不会因为执行实体的生命周期改变而改变。比如所有硬件资源,执行实体在运行时既不能创建,也不能销毁,要使用这些资源时必须要按照请求资源、使用资源、释放资源这样的顺序操作。

               【临时性资源】:也叫消耗性资源,这些资源是由实体在运行过程中动态创建和销毁的。如硬件中断信号、缓冲区内的消息、队列中的任务等,都是属于临时性资源,通常是由一个执行实体创建出来,之后被另外的执行实体处理后销毁。比如典型的消息中间件的使用,也就生产者-消费者模型。

               【可抢占式资源】:也叫可剥夺资源,是指一个执行实体在获取到某个资源后,该资源是由可能被其他实体或系统剥夺走的。比如:

                                进程级别:CPU、主内存等资源;

                                线程级别:比如Java中的ForkJoin框架中的任务。

               【不可抢占式资源】:也叫不可剥夺,指把一个执行实体获取到资源之后,系统或程序不能强行收回,只能在实体使用完成之后自行释放。比如:

                                进程级别:磁带机,打印机等,分配给进程后只能等进程使用完自动释放;

                                线程级别:锁资源就是典型的线程级别的不可剥夺性资源,当一条线程获取到锁资源还有。

                                                  其他线程就不能剥夺该资源,只能由获取到锁资源的线程自动释放。

       9、死锁处理

             处理死锁可以归纳为以下四个角度:

  1. 预防死锁:通过代码设计或更改配置来破坏掉死锁产生的四个条件其中之一,达到预防死锁的目的;
  2. 避免死锁: 在资源分配的过程中,尽量保证资源请求的顺序性,防止推进顺序不当引起的死锁问题;
  3. 检测死锁:允许系统在运行过程中发生死锁情况,但可以设置检测机制及时检测死锁的发生,并采取适当措施加以清除;
  4. 解除死锁:当检测出死锁后,采用适当的措施将进程从死锁状态中解脱出来。

      

 

                                

 

    

posted on 2022-04-22 17:32  木乃伊人  阅读(217)  评论(0编辑  收藏  举报

导航