0


Linux下的多线程编程:原理、工具及应用(2)

  •                                           ***🎬慕斯主页***:*******************************************************************************************************************************修仙—别有洞天********************************************************************************************************************************
    
                                            ♈️*今日夜电波:*Flower of Life—陽花**
    
                                                              0:34━━━━━━️💟──────── 4:46
                                                                   🔄   ◀️   ⏸   ▶️    ☰  
    
                                    💗关注👍点赞🙌收藏*您的每一次鼓励都是对我莫大的支持*😍
    

理解互斥锁

    通过上一篇对于互斥锁操作的基本理解,现在我们从底层理解一下互斥锁的原理:

前置知识

    1、为了实现互斥锁操作,大多数体系结构都提供了**swap或exchange**指令,**该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性** 。
     2、从上面对于锁的理解,我们可以知道,锁其实是一个自定义的结构体类型。现在我们可以先这样**简单的理解**一下这个结构体类型,真正的结构体类型肯定是包涵很多安全属性之类的:
struct 
{
  int mutex=1;
}
    3、寄存器硬件在**CPU内部只有一套,但是寄存器的内容每一个线程都有一份**,拥有属于自己的上下文!

通过伪代码进行理解

    知道了上面的前置知识后,我们通过以下的伪代码真正的理解:

lock:

    注意**上面的汇编语句都是原子性的!**可以看到第一段movb $0,%a1表示为将数值0移动到寄存器 %a1 的最低字节,就是初始化%a1为0.然后通过xchgb %a1,mutex交换寄存器和内存单元中的数据。由于我们的锁的定义实际上就是一个自定义类型的定义,因此,他当然是**存储在内存中**的啦!我们可以通过以下图示来理解:

    接着继续理解,下面是一段if判断的伪代码,判断al寄存器中的内容是否大于0,如果我们的线程还在**时间片内,那么他就是大于0的会返回0**,当**时间片用完,那么我们的其他寄存器等会保存上下文**。新的进程进来了,同样有锁,需要初始化al为0,然后交换内存中的数据,但是!**重要的来了,此时内存中的mutex是0!我们换后还是0!**,此时进行i**f判断,就会挂起等待!!!这就是加锁的底层原理!**

unlock:

    解锁可以看到movb $1,%a1就是初始化%a1为1,然后唤醒被因Mutex挂起等待的线程即可。

死锁

    死锁是指在一组进程中的**各个进程均占有不会释放的资源**,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态。

###死锁四个必要条件

互斥条件:一个资源每次只能被一个执行流使用

请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放

不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺

循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系

###避免死锁

破坏死锁的四个必要条件

加锁顺序一致

避免锁未释放的场景

资源一次性分配

线程同步的概念

###条件变量

当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。

例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。

####同步概念与竞态条件

同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步。

竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件。在线程场景下,这种问题也不难理解

生产者消费者模型

概述

    生产者消费者模型,我们可以这样理解:有一间商店,消费者要去买东西,生产者要供货。而在这之间我们可以**将生产者与消费者分别理解为线程**,而**商店理解为内存空间**(基于特定空间的数据结构或者容器),而这个商店是用于进行**执行流间数据的传递的!**可以理解为**临界资源!**在这个模型中,我们需要保证**生产消费的过程是安全的!**因此我们需要遵守以下的原则:可以简称为“321原则”。

1、3种关系:(本质就是用锁&&条件变量维护)

生产者与生产者之间是什么关系?竞争—互斥

消费者与消费者之间是什么关系?竞争—互斥

生产者与消费者之间是什么关系?互斥&&同步

2、2种角色

生产者(1 or n)、消费者(1 or n) --线程或者进程

3、1个交易场所

内存空间

如何理解CP问题?

    在生产者消费者模型中,CP问题通常指的是生产者和消费者之间的同步问题,即**如何保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区空时取出数据**。这个问题可以通过使用多线程编程中的同步机制来解决。具体来说,有以下几个方面:
  1. 共享缓冲区:生产者和消费者共享一个固定大小的缓冲区,生产者向其中放入数据,消费者从其中取出数据。
  2. 同步机制:为了避免生产者在缓冲区满时继续生产数据,以及消费者在缓冲区空时继续消费数据,需要使用同步机制。常见的同步机制包括互斥锁(mutex)和条件变量(condition variable)。互斥锁用于保护共享资源,防止同时读写;条件变量则用于实现线程间的等待和通知机制。
  3. 状态一致性:需要确保在任何时刻,缓冲区的数据状态都是一致的。这意味着在消费者取出数据后,必须及时更新缓冲区的状态,以防止其他消费者错误地认为还有数据可取。
  4. 生产者限制:生产者在生产数据时,需要检查缓冲区是否已满。如果缓冲区已满,生产者必须等待,直到消费者取出数据并通知生产者可以继续生产。
  5. 消费者限制:消费者在消费数据时,需要检查缓冲区是否为空。如果缓冲区为空,消费者必须等待,直到生产者放入数据并通知消费者可以继续消费。
  6. 编码实践:在实现生产者-消费者模型时,需要注意避免竞态条件和死锁。正确的使用同步机制是解决这些问题的关键。
  7. 实际应用:生产者-消费者模型在分布式系统、并发编程和实时系统中非常常见,它是理解和解决并发问题的基础模型之一。

再次理解条件变量

    生产者消费者模型会有数据不一致的问题,上面提到为了满足线程同步,光加锁是不够的,可能会有饥饿的问题。因此,我们**需要定义“条件变量”来让线程不做无效的锁申请,执行具有顺序**。我们可以先简单的用以下的结构体先理解条件变量:
struct cond
{
  //条件是否就绪
  int flag;
  //维护一个线程队列
  tcb_queue;
}

  **                感谢你耐心的看到这里ღ( ´・ᴗ・` )比心,如有哪里有错误请踢一脚作者o(╥﹏╥)o!** 

                                   ![](https://img-blog.csdnimg.cn/a2296f4aa7fd45e9b1a1c44f9b8432a6.gif)

** 给个三连再走嘛~ **

标签: linux 运维 服务器

本文转载自: https://blog.csdn.net/weixin_64038246/article/details/136757326
版权归原作者 慕斯( ˘▽˘)っ 所有, 如有侵权,请联系我们删除。

“Linux下的多线程编程:原理、工具及应用(2)”的评论:

还没有评论