【Linux系统】线程的互斥

news2025/6/18 7:42:39

互斥

互斥概念

多个线程能够看到的资源称为共享资源,那我们需要对这种资源进行保护,需要用到互斥!!!

见一见多线程访问的问题 --- 抢票问题

int tickets = 1000;

void route(const std::string& name)
{
    while(true)
    {
        if(tickets > 0)
        {
            //抢票过程
            usleep(1000); //ms -> 模拟抢票花费的时间
            printf("who:%s, tickets:%d\n", name.c_str(), tickets);
            tickets--;
        }
        else
        {
            break;
        }
    }
}
int main()
{
    Thread t1("Thread-1", route);
    Thread t2("Thread-2", route);
    Thread t3("Thread-3", route);
    Thread t4("Thread-4", route);

    t1.Start();
    t2.Start();
    t3.Start();
    t4.Start();

    t1.Join();
    t2.Join();
    t3.Join();
    t4.Join();
    return 0;
}

 

理论上我们设定的是1000张票,枪到0结束,就不应该会出现0,-1,-2这种情况出现!!!

为什么??? 

 认识和分析为什么会出现抢到负数的问题???

我们在判断票数时,判断的过程是不是计算?CPU是要进行调度计算的,那如何计算?

计算机的运算类型:1、算术运算 2、逻辑运算

 CPU内,寄存器只有一套,但是寄存器里面的数据,可以有多套,属于线程私有,看起来放在了一套公共的寄存器内,但是属于线程私有,当他被切换的时候,他要带走自己的数据的!!!

回来的时候会恢复!!!而我们在执行票数--的操作时,其实在寄存器内部是要执行三步的,1.重读数据、2.--数据、3.读回数据,所以当我们有四个线程,a、b、c、d在跑时,a线程到达判断票树那行代码,此时票数为1,正常进入到达printf时,时间片轮转,b进程切换进来,此时b的票数读入的也是1,又到达printf等待打印,时间片再次轮转,进程切换到c,再轮转切换到进程d,那我们想怎样tickets再到达1时又串行的被--了四次,不就出现了负数!!!

如何解决呢?

 互斥锁

认识锁和它的接口

Ubuntu系统手册查不到,Centos可以

其中pthread_mutex_t为互斥锁类型 -->任何时刻只允许一个线程进行资源访问,其中第一个接口只需传入我们设置的锁即可,第二个接口传入锁,第二个参数是各种属性,我们设为nullptr即可。

 pthread_mutex_t 可设置为全局的,也可设置为局部,若是全局的或者是静态的,无需destroy。

 

 我们常用第一个接口加锁,第三个解锁,第二个表示可在任何时刻解锁。

所谓的对临界资源进行保护,本质是对临界区代码进行保护!

我们对所有资源进行访问,本质都是通过代码进行访问的!所以保护资源,本质就是把访问资源的代码保护起来!

加锁和解锁本质就是把多线程从并行执行变为串行执行。 

 

 如果我们把加锁放在while循环外面会如何?我们多线程本来就是为了并发执行提高效率的,我们加到while外面,那循环里面不就变成了串行执行了吗,效率极低!!!

1.所以,加锁的范围,粒度一定要尽量的小,即代码行数尽量少!!!

2.任何线程要进行抢票,都得先申请锁,原则上不应该有例外

3.所有线程申请锁,前提是所有线程得看到这把锁,锁本身也是共享资源,加锁的过程,必须是原子的!!!

4.原子性:要么不做,要做就做完,没有中间状态,就是原子性。

5.如果线程申请锁失败了,我的线程要被阻塞

6.如果线程申请锁成功了,继续向后运行。

7.如果线程申请锁成功了,执行临界区的代码了, 执行临界区代码可以切换吗???

可以,其他线程无法进入,因为我虽然被切换了,但是我没有释放锁啊!!!我可以放心的执行完毕,没有人能打扰我!

结论:所以对于其他线程,要么我没有申请锁,要么我释放了锁,对其他线程才有意义

我访问临界区,对其他线程是原子的!!!

 下面是把锁设为局部锁的代码

 mythread.hpp

#pragma once
#include<iostream>
#include<string>
#include<pthread.h>

namespace ThreadMoudle
{
    class ThreadData
    {
    public:
        ThreadData(const std::string &name, pthread_mutex_t *lock):_name(name),_lock(lock)
        {

        }
    public:
        std::string _name;
        pthread_mutex_t *_lock;
    };
    //线程要执行的方法
    typedef void (*func_t)(ThreadData *td);

    class Thread
    {
    public:
        void Excute()
        {
            std::cout << _name << "is running" << std::endl;
            _isrunning = true;
            _func(_td);
            _isrunning = false;
        }
    public:
        Thread(const std::string& name, func_t func, ThreadData *td) :_name(name), _func(func), _td(td)
        {
            std::cout << "create" << name << "done" << std::endl;
        }
        static void* ThreadRoutine(void* args)//新线程都会执行该方法
        {
            Thread* self = static_cast<Thread*>(args);//获得了当前对象
            self->Excute();
            return nullptr;
        }
        bool Start()
        {
            int n = ::pthread_create(&_tid, nullptr, ThreadRoutine, this);
            if(n != 0) return false;
            return true;
        }
        std::string Status()
        {
            if(_isrunning) return "running";
            else return "sleep";
        }
        void Stop()
        {
            if(_isrunning)
            {
                ::pthread_cancel(_tid);
                _isrunning = false;
                std::cout << _name << "Stop" << std::endl;
            }
        }
        std::string Name()
        {
            return _name;
        }
        void Join()
        {
            ::pthread_join(_tid, nullptr);
            std::cout << _name << "Joined" << std::endl;
            delete _td;
        }
        ~Thread()
        {

        }
    public:
        std::string _name;
        pthread_t _tid;
        bool _isrunning;
        func_t _func; //线程要执行的回调函数

        ThreadData *_td;
    };   
};

 main.cc

static int num = 5;
int tickets = 1000;

void route(ThreadData* td)
{
    while (true)
    {
        pthread_mutex_lock(td->_lock);
        if (tickets > 0)
        {
            // 抢票过程
            usleep(1000); // ms -> 模拟抢票花费的时间
            printf("who:%s, tickets:%d\n", td->_name.c_str(), tickets);
            tickets--;
            pthread_mutex_unlock(td->_lock);
        }
        else
        {
            pthread_mutex_unlock(td->_lock);
            break;
        }
    }
}
int main()
{
    pthread_mutex_t mutex;
    pthread_mutex_init(&mutex, nullptr);
    std::vector<Thread> threads;
    for(int i = 0; i < num; i++)
    {
        std::string name = "Thread-" + std::to_string(i+1);
        ThreadData* td = new ThreadData(name, &mutex);
        threads.emplace_back(name, route, td);
    }

    for(auto& thread:threads)
    {
        thread.Start();
    }

    for(auto& thread:threads)
    {
        thread.Join();
    }

    pthread_mutex_destroy(&mutex);
    return 0;
}

也可以对锁进行封装,利用局部对象出作用域析构的特性加锁解锁!

Lockguard .hpp

#pragma once
#include<pthread.h>

class LockGuard
{
public:
    LockGuard(pthread_mutex_t *mutex):_mutex(mutex)
    {
        pthread_mutex_lock(mutex);
    }
    ~LockGuard()
    {
        pthread_mutex_unlock(_mutex);
    }
private:
    pthread_mutex_t *_mutex;
};

 main.cc

static int num = 5;
int tickets = 1000;

void route(ThreadData* td)
{
    while (true)
    {
        LockGuard lockguard(td->_lock);
        if (tickets > 0)
        {
            // 抢票过程
            usleep(1000); // ms -> 模拟抢票花费的时间
            printf("who:%s, tickets:%d\n", td->_name.c_str(), tickets);
            tickets--;
        }
        else
        {
            break;
        }
    }
}
int main()
{
    pthread_mutex_t mutex;
    pthread_mutex_init(&mutex, nullptr);
    std::vector<Thread> threads;
    for(int i = 0; i < num; i++)
    {
        std::string name = "Thread-" + std::to_string(i+1);
        ThreadData* td = new ThreadData(name, &mutex);
        threads.emplace_back(name, route, td);
    }

    for(auto& thread:threads)
    {
        thread.Start();
    }

    for(auto& thread:threads)
    {
        thread.Join();
    }

    pthread_mutex_destroy(&mutex);
    return 0;
}

原理角度如何理解 这个锁呢?

pthread_mutex_lock(&mutex);

如何理解申请锁成功,运行你进入临界区?这是因为申请锁成功,pthread_mutex_lock函数会返回

如何理解申请锁成功,不允许你进入临界区?这是因为申请锁失败,pthread_mutex_lock函数不会返回,线程就阻塞了!

而unlock就是 在lock内部被唤醒,重新申请锁!

 互斥锁的底层实现理解

 所以我们加锁时汇编层是需要把CPU内部的叫%al的寄存器清0,然后使用echgb指令把内存中的lock与CPU寄存器中的数据进行交换,(交换不是拷贝,只有一个“1”,持有“1”的就代表持有锁)这样该线程就拿到了一把锁!!!所以本质上xchgb这一条汇编指令就代表加锁!!!

 

1.CPU 的寄存器只有一套,被所有的线程共享,但是寄存器里面的数据,属于执行流的上下文,属于执行流私有的数据!

2.CPU在执行的时候,一定要有对应的执行载体 --- 线程&&进程

3.数据再内存中,是被所有线程所共享的!!

结论:把数据从内存移动到CPU寄存器中,本质是把数据从共享,变成线程私有!!!

 所以加锁和解锁过程就是,第一个进去的线程把1从内存中交换到寄存器中,如果执行到判断代码,刚好时间片到了,进行切换,该线程就把自己的数据拿走,等下个时间片拿回来,在这段时间内,任何别的线程到达判断位置,都拿不到这个1,最后就会走else进行挂起等待,解锁时就把内存中的锁重新置回1,然后唤醒等待的线程继续执行即可!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1955472.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Docker容器的数据管理

转载说明&#xff1a;如果您喜欢这篇文章并打算转载它&#xff0c;请私信作者取得授权。感谢您喜爱本文&#xff0c;请文明转载&#xff0c;谢谢。 我们在使用Docker的过程中&#xff0c;往往需要能查看容器内应用产生的数据&#xff0c;或者需要把容器内的数据进行备份&#x…

python学习笔记——数字

一、数字概述 1.Python 数字数据类型用于存储数值。 数据类型是不允许改变的&#xff0c;这就意味着如果改变数字数据类型的值&#xff0c;将重新分配内存空间。 2.可以使用del语句删除一些数字对象的引用。 del var1[,var2[,var3[....,varN]]]3.可以通过使用del语句删除单个或…

【python014】Python爬取并解析潮汐天气简报-潮历数据

1.欢迎点赞、关注、批评、指正&#xff0c;互三走起来&#xff0c;小手动起来&#xff01; 【python014】Python爬取并解析潮汐天气简报-潮历数据&#xff0c;源代码下载【python014】Python爬取并解析潮汐天气简报-潮历数据&#xff0c;源代码下载【python014】Python爬取并解…

Aria2 任意文件写入漏洞

目录 Aria2介绍漏洞描述漏洞复现 Aria2介绍 Aria2是一个在命令行下运行&#xff0c;多协议&#xff0c;多来源下载工具&#xff08;HTTP / HTTPS&#xff0c;FTP&#xff0c;BitTorrent&#xff0c;Metalink&#xff09;&#xff0c;内建XML-RPC用户界面。Aria提供RPC服务器&a…

2.9.GoogLeNet

GoogLeNet ​ 主要解决了什么样大小的卷积核是最合适的&#xff1a;有时使用不同大小的卷积核组合是有利的 1.Inception块 ​ Inception块由四条并行路径组成。 前三条路径使用窗口大小为11、33和55的卷积层&#xff0c;从不同空间大小中提取信息。 ​ 中间的两条路径在输入…

任务管理无忧:2024年最佳7款待办事项管理软件

本文将分享2024年值得关注的7大优质待办事项管理软件&#xff1a;PingCode、Worktile、滴答清单、时光序、好用便签、Todoist、ClickUp。 在寻找完美的待办事项管理工具时&#xff0c;你是否感觉到选择众多却难以决断&#xff1f;无论是保持日程有序&#xff0c;还是优化团队协…

Linux基本用法(上)

1.计算机主要由 硬件和软件 组成 2.操作系统是什么 ? 有什么作用&#xff1f; 操作系统是软件的一类 主要作用是协助用户调度硬件工作&#xff0c;充当用户和计算机之间的桥梁 3.常见的操作系统有哪些? PC端: Windows&#xff0c;Linux,MacOS 移动端: Android&#xff…

UART 通信协议

文章目录 一 简介二 电平标准三 引脚定义四 数据格式五 波特率 一 简介 ​ UART (Universal Asynchronous Receiver/Transmitter)&#xff0c;通用异步收发器&#xff0c;是一种串行、异步、全双工通信协议。 串行&#xff1a;利用一条传输线&#xff0c;将数据一位一位地传送…

YOLOv8改进 | 主干网络 | ⭐重写星辰Rewrite the Stars⭐【CVPR2024】

秋招面试专栏推荐 &#xff1a;深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 &#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 专栏目录 &#xff1a;《YOLOv8改进有效…

vulntarget-a

实际部署之后的win7 ip: 192.168.127.128 具体攻击过程如下 win7 扫描服务 使用fscan扫描win 7中的服务以及漏洞 ./fscan -h 192.168.127.128 扫出来一个ms17-010以及通达oa的漏洞&#xff0c;既然有永恒之蓝的&#xff0c;直接上MSF就行了 msf6 > search ms17-010 msf6…

架子鼓鼓谱制谱什么软件好 Guitar Pro怎么编鼓谱

对那些想学架子鼓又缺乏预算的朋友来说&#xff0c;通过架子鼓谱子软件来模拟学习也是个不错的选择。不过&#xff0c;由于市面上类似的软件过多&#xff0c;许多人都挑花了眼&#xff01;那么&#xff0c;架子鼓谱子软件哪个比较好用呢&#xff1f;下面小编就来推荐几款好用的…

HTML--JavaScript操作DOM对象

目录 本章目标 一.DOM对象概念 ​编辑 二.节点访问方法 常用方法&#xff1a; 层次关系访问节点 三.节点信息 四.节点的操作方法 操作节点的属性 创建节点 删除替换节点 五.节点操作样式 style属性 class-name属性 六.获取元素位置 总结 本章目标 了解DOM的分类和节…

【论文精读】 | 基于图表示的视频抑郁症识别的两阶段时间建模框架

文章目录 0、Description1、Introduction2、Related work2.1 Relationship between depression and facial behaviours2.2 Video-based automatic depression analysis2.3 Facial graph representation 3、The proposed two-stage approach3.1 Short-term depressive behaviour…

18746 逆序数

这个问题可以使用归并排序的思想来解决。在归并排序的过程中&#xff0c;我们可以统计逆序数的数量。当我们合并两个已经排序的数组时&#xff0c;如果左边的数组中的元素&#xfffd;&#xfffd;于右边的数组中的元素&#xff0c;那么就存在逆序&#xff0c;逆序数的数量就是…

力扣SQL50 按分类统计薪水 条件统计

Problem: 1907. 按分类统计薪水 文章目录 思路Code 思路 &#x1f468;‍&#x1f3eb; 参考题解 Code SELECT Low Salary AS category,SUM(CASE WHEN income < 20000 THEN 1 ELSE 0 END) AS accounts_count FROM AccountsUNION SELECT Average Salary category,SUM(C…

DBus快速入门

DBus快速入门 参考链接&#xff1a; 中文博客&#xff1a; https://www.e-learn.cn/topic/1808992 https://blog.csdn.net/u011942101/article/details/123383195 https://blog.csdn.net/weixin_44498318/article/details/115803936 https://www.e-learn.cn/topic/1808992 htt…

Vue3 Pinia的创建与使用代替Vuex 全局数据共享 同步异步

介绍 提供跨组件和页面的共享状态能力&#xff0c;作为Vuex的替代品&#xff0c;专为Vue3设计的状态管理库。 Vuex&#xff1a;在Vuex中&#xff0c;更改状态必须通过Mutation或Action完成&#xff0c;手动触发更新。Pinia&#xff1a;Pinia的状态是响应式的&#xff0c;当状…

JAVA中的异常:异常的分类+异常的处理

文章目录 1. 异常的分类1.1 Error1.2 Exception1.2.1 运行时异常1.2.2 非运行时异常 2.异常的处理2.1 try catch用法2.2 throws和throw的区别 1. 异常的分类 Throwable类&#xff08;表示可抛&#xff09;是所有异常和错误的超类&#xff0c;两个直接子类为Error和Exception分…

C++11新特性——智能指针——参考bibi《 原子之音》的视频以及ChatGpt

智能指针 一、内存泄露1.1 内存泄露常见原因1.2 如何避免内存泄露 二、实例Demo2.1 文件结构2.2 Dog.h2.3 Dog.cpp2.3 mian.cpp 三、独占式智能指针:unique _ptr3.1 创建方式3.1.1 ⭐从原始(裸)指针转换&#xff1a;3.1.2 ⭐⭐使用 new 关键字直接创建&#xff1a;3.1.3 ⭐⭐⭐…

敏感信息泄露wp

1.右键查看网页源代码 2.前台JS绕过&#xff0c;ctrlU绕过JS查看源码 3.开发者工具&#xff0c;网络&#xff0c;查看协议 4.后台地址在robots,拼接目录/robots.txt 5.用dirsearch扫描&#xff0c;看到index.phps,phps中有源码&#xff0c;拼接目录&#xff0c;下载index.phps …