【Linux】线程互斥

📝前言:
这篇文章我们来讲讲Linux——线程互斥

🎬个人简介:努力学习ing
📋个人专栏:Linux
🎀优快云主页 愚润求学
🌄其他专栏:C++学习笔记C语言入门基础python入门基础C++刷题专栏


一,什么是线程互斥

1. 背景概念回顾

  • 临界资源:多线程执行流共享的资源就叫做临界资源
  • 临界区:每个线程内部,访问临界资源的代码,就叫做临界区
  • 互斥:任何时刻,保证有且只有⼀个执行流进入临界区,访问临界资源【互斥通常对临界资源起保护作用】
  • 原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成

2. 没有互斥的代码示例

2.1 示例

当线程之间,并发的操作共享变量,且没有互斥量mutex(锁)保护的时候,就可能出现数据不一致问题。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <iostream>
#include <vector>

int tickets = 1000; // 总用一千张票

void* buyticket(void* args)
{
    std::string name = static_cast<char*>(args);
    while(true)
    {
        if(tickets > 0)
            std::cout << name << " buy ticket: " << tickets-- << std::endl;
        else
            break;
    }
    return nullptr;
}

int main()
{
    pthread_t threads[5];
    for(int i = 1; i <= 5; i++)
    {
        char name[64];
        snprintf(name, sizeof(name), "thread-%d", i);
        pthread_create(&threads[i - 1], nullptr, buyticket, name);
    }
    for(int i = 1; i <= 5; i++)
    {
        pthread_join(threads[i - 1], nullptr);
    }
    return 0;
}

运行结果:
在这里插入图片描述
很明显,票数减多了。为什么呢?

2.2 解释现象

理解代码与指令

我们的tickets--操作,变成汇编其实是三条指令。
在这里插入图片描述
在以上三条指令中间,线程都有可能被切换,当线程被切换,线程会把 ebx 和 PC 寄存器里的上下文数据保存到自己的PCB里面,然后离开。(即下一个线程可覆盖原来寄存器里的数据)

比如,原始票数为1000,当进程 A 执行完 1,2步(此时ticket应该为999),结果刚好被切换了, 进程 B 从第一步开始执行,因为进程 A 的999没有写回内存,所以进程 B 载入的也是1000,这就导致了内存不一致。

解释为什么出现负票

在这里插入图片描述

  • 问题在于if判断,假如这5个线程在票数为1的时候,依次进行了if判断,而且刚好都在if判断完以后就立马切换成下一个线程判断,则所有线程都是ticket == 1的时候通过的if判断。
  • 所有进程都会进入if语句去执行buy ticket的操作。
  • 然后这时候执行buy ticket的操作是串行的(一个一个线程执行),比如,当线程1执行完(ticket == 1载入,计算得0,把0写回)
  • 线程2因为已经过了if所以也要执行,这时候线程2载入的ticket == 0,经过一系列操作ticket就会变成负数

二,互斥量mutex

要解决上面的问题,我们就要使用互斥量mutex(也是一个变量,也存储值,也存储在内存里面),也叫做
在这里插入图片描述

接口(pthread库的)

1. 初始化

静态分配(全局初始化),会自动销毁

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
  • pthread_mutex_tmutex的类型

动态分配,谁定义谁销毁

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr)
  • mutex:指向要初始化的锁(已经分配好内存的)
  • attr:传入NULL,表示使用默认属性的锁

2. 销毁

 int pthread_mutex_destroy(pthread_mutex_t *mutex)
  • 不要销毁⼀个已经加锁的互斥量
  • 已经销毁的互斥量,要确保后⾯不会有线程再尝试加锁

3. 加锁和解锁

一般,如果多个线程访问同一个临界区,则这多个线程竞争的应该是同一把锁。

加锁

int pthread_mutex_lock(pthread_mutex_t *mutex);

如果锁被占用(或者没有竞争过其他执行流),加锁不成功就会阻塞(执⾏流被挂起)

解锁

int pthread_mutex_unlock(pthread_mutex_t *mutex);
  • 返回值:成功返回0,失败返回错误号

当然,C++也有专门的一套锁的方法(封装的pthread),接口更简单,方便用户使用

示例(解决抢票问题)

    while (true)
    {
        pthread_mutex_lock(&mutex);
        if (tickets > 0)
        {
            std::cout << name << " buy ticket: " << tickets-- << std::endl;
            pthread_mutex_unlock(&mutex);
        }
        else
        {
            pthread_mutex_unlock(&mutex);
            break;
        }
    }

输出结果:
在这里插入图片描述
为什么全是线程 5 抢的票,这是因为,当线程 5 解锁以后,马上又进入下一个循环申请锁了,而其他线程还要“唤醒”等等操作,线程 5 最近,所以线程 5 一直竞争成功。这也导致了其他线程的饥饿问题(下一篇文章会讲述)

三,mutex原理

要解决的问题就是:当一个线程竞争到锁以后,访问临界区的时候,其他线程不能进入临界区。

1. 硬件实现

在一个线程竞争到锁以后,关闭时钟中断,让线程无法切换,这样其他线程就不会进入临界区了

2. 软件实现

通过将内存中mutex的值与当前竞争到锁的线程的exb的值交换,使得,只有当前线程的硬件上下文里面的mutex1

以抢票的代码为例

  • 首先,mutex是全局变量,mutex在内存中原来存储的值是1
  • 对于每个进程,申请锁,并判断能不能进入临界区的汇编分为两步
    • 第一步(申请锁):把 0 传到 寄存器%al,然后把%al的内容和内存中mutex的内容做交换
    • 第二步(判断):如果al的内容 > 0就代表当前线程申请到锁了,进入临界区,否则挂起等待

在这里插入图片描述

  • 如果线程 A 竞争到锁了,原来mutex的值是1,交换%al和内存mutex的值,线程 A 的%al中存储的就是 1 了,mutex内存中就是 0了。就算此时线程 A 被切换,线程 A 也能带着 %al中的 1这个上下文被切换。
  • 此时,其他 线程再来申请锁,因为内存中的mutex的值已经是 0了,所以无论怎么交换,得到的都是0,过不了判断,无法进入临界区
  • 对于线程 A ,过了判断后进入临界区,%al寄存器的值被覆盖了也没事,恢复锁的时候,直接往内存的mutex写回 1
  • 然后唤醒其他等待mutex的线程,再竞争

在这里插入图片描述

四,mutex封装

我们像语言层一样,封装系统的mutex

#pragma once
#include <pthread.h>

class Mutex
{
public:
    Mutex()
    {
        pthread_mutex_init(&_mutex, nullptr);
    }
    ~Mutex()
    {
        pthread_mutex_destroy(&_mutex);
    }

    void Lock()
    {
        pthread_mutex_lock(&_mutex);
    }
    void Unlock()
    {
        pthread_mutex_unlock(&_mutex);
    }
private:
    pthread_mutex_t _mutex;
};

🌈我的分享也就到此结束啦🌈
要是我的分享也能对你的学习起到帮助,那简直是太酷啦!
若有不足,还请大家多多指正,我们一起学习交流!
📢公主,王子:点赞👍→收藏⭐→关注🔍
感谢大家的观看和支持!祝大家都能得偿所愿,天天开心!!!

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

愚润泽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值