Linux学习之路 -- 线程 -- 互斥

news2024/11/15 8:56:02

目录

1、概念引入

2、互斥锁

1、pthread_mutex_init && pthread_ mutex_destory 

2、pthread_mutex_lock && pthread_mutex_unlock

3、互斥锁原理的简单介绍


1、概念引入

为了介绍线程的同步与互斥,我们以抢票逻辑引入相关的概念。

示例代码:

#include<iostream>
#include<vector>

int g_ticket = 1000;


void* funtion(void* arg)
{
    while(1)
    {
        if(g_ticket > 0)
        {
            //模拟抢票逻辑
            std::cout << "g_ticket : " << g_ticket << " &g_ticket : " << &g_ticket << std::endl;
            g_ticket--;
        }
        else
        {
            break;
        }
    }
    return nullptr;
}
const int num = 5;
int main()
{
    std::vector<pthread_t> pthread;
    for(int i = 0; i < num; i++)
    {
        pthread_t id;
        pthread_create(&id,nullptr,funtion, nullptr);
        pthread.emplace_back(id);
    }
    for(auto& e: pthread)
    {
        pthread_join(e,nullptr);
    }
    return 0;
}

我们创建多个线程,然后让不同的线程对同一个变量进行操作。也就是让不同线程访问同一个函数,抢票函数会让票数减减。我们运行一下代码,观察一下结果。

这里我们可以看见,g_ticket的地址是一样的,说明访问的变量一定是同一全局变量。而g_ticket的数值却出现-2,-3。这明显是不符合我们的要求的,所以这里一定是有问题的,而这也叫数据不一致 。

首先解释一下为什么会造成这种现象,首先if中的条件判断是逻辑判断,这是要在cpu内进行的运算的。(我们假设g_ticket的值为1)

每当一个线程在将内存中g_ticket数据放入cpu中的寄存器,由cpu执行判断逻辑运算。在判断成功后,该线程就有可能直接被切走了,没有执行到下面的g_ticket--操作,而内存中的g_ticket还是1。下个线程也会重复上述的操作,这就导致虽然只有一张票,但却还是有多个线程进入该函数中。线程同时执行了ticket--操作,所以就会造成ticket被减到负数的情况。同时,需要注意的是,ticket--也不是原子的,这段代码在cpu中的执行过程分为三步,第一步是将内存中g_ticket读取到cpu中,第二步是将cpu中的g_ticket做减减操作,第三步是将cpu中的值写回内存中。这里的每一步在结束后,线程可能都会被切走,这也会造成g_ticket这个数据不安全,不过这里一般出错概率较低,主要还是上述因素造成的。这种因为原子性问题导致数据不一致情况还是比较常见的。(原子性:只有执行和没执行两种状态,说通俗一点就是翻译成汇编只有一条语句,上面的++操作翻译成汇编就有3条语句) 总结一下,这里g_ticket出现负数情况的原因,其实就是这个共享资源没有被保护,并且访问该共享资源的过程并不是原子性的。

2、互斥锁

如何解决上述的问题呢?这里我们就需要引入锁的概念了。原生线程库不仅提供了线程创建等相关的接口,还提供了互斥锁的相关接口。我们在线程中常用的锁一般称为互斥锁,互斥的概念前面已经有所介绍,这里不再赘述。我们只要对共享资源进行加锁,就能防止出现上述的问题。下面先介绍一下相关的接口。

1、pthread_mutex_init && pthread_ mutex_destory 

在使用锁之前,我们首先需要定义一个pthread_mutex_t类型的变量。如果这个变量是局部变量,我们就需要使用pthread_mutex_init进行初始化(这里的第二参数表示锁的属性,这里定义成nullptr即可),同时在使用完后,要对锁进行pthread_mutex_destory释放。如果这个变量是全局变量,则我们只需要在定义变量时,让其等于PTHREAD_MUTEX_INITALIZER进行初始化即可,后面不需要手动进行销毁。


2、pthread_mutex_lock && pthread_mutex_unlock

当我们初始化锁以后,我们就需要使用锁了,pthread_mutex_lock 就表示申请上锁,申请成功,函数返回,继续向后执行;申请失败,一直阻塞直至申请成功;如果函数调用失败,出错返回。而trylock接口在申请失败后会直接返回,这是和lock接口的区别。而unlock就表示解开该锁。

下面演示一下加锁例子,我们以上面抢票逻辑的代码为例子。

ThreadMode.hpp

#ifndef __THREAD_HPP__
#define __THREAD_HPP__

#include <iostream>
#include <string>
#include <unistd.h>
#include <functional>
#include <pthread.h>

namespace ThreadModule
{
    template<typename T>
    using func_t = std::function<void(T)>;
    // typedef std::function<void(const T&)> func_t;

    template<typename T>
    class Thread
    {
    public:
        void Excute()
        {
            _func(_data);
        }
    public:
        Thread(func_t<T> func, T data, const std::string &name="none-name")//右值
            : _func(func), _data(data), _threadname(name), _stop(true)
        {}
        static void *threadroutine(void *args) // 类成员函数,形参是有this指针的!!
        {
            Thread<T> *self = static_cast<Thread<T> *>(args);
            self->Excute();
            return nullptr;
        }
        bool Start()
        {
            int n = pthread_create(&_tid, nullptr, threadroutine, this);
            if(!n)
            {
                _stop = false;
                return true;
            }
            else
            {
                return false;
            }
        }
        void Detach()
        {
            if(!_stop)
            {
                pthread_detach(_tid);
            }
        }
        void Join()
        {
            if(!_stop)
            {
                pthread_join(_tid, nullptr);
            }
        }
        std::string name()
        {
            return _threadname;
        }
        void Stop()
        {
            _stop = true;
        }
        T& Data()
        {
            return _data;
        }
        ~Thread() {}

    private:
        pthread_t _tid;
        std::string _threadname;
        T _data;  // 为了让所有的线程访问同一个全局变量
        func_t<T> _func;
        bool _stop;
    };
} // namespace ThreadModule

#endif
#include <iostream>
#include "ThreadMode.hpp"
#include <vector>

int g_ticket = 1000;
using namespace ThreadModule;
template <class T>
class ThreadData
{
public:
    ThreadData(int &data, const std::string str) : _data(data), name(str), total(0)
    {
    }
    ~ThreadData()
    {
    }
    std::string Getname()
    {
        return name;
    }
    void buyticket()
    {
        _data--;
    }
    int Geticket()
    {
        return _data;
    }
    void Plus()
    {
        total++;
    }
    void Total()
    {
        std::cout << name << " : " << total << std::endl;
    }

private:
    int &_data;
    std::string name;
    int total;
};
pthread_mutex_t _lock = PTHREAD_MUTEX_INITIALIZER;//全局锁
void funtion(ThreadData<int> *td)
{
    while (1)
    {
        //加锁
        pthread_mutex_lock(&_lock);
        if (g_ticket > 0)
        {
            std::cout << td->Getname() << " get ticket, remain ticket number: " << td->Geticket() << std::endl;
            td->buyticket();
            pthread_mutex_unlock(&_lock);
            td->Plus();
        }
        else
        {
            pthread_mutex_unlock(&_lock);
            break;
        }
    }
}
const int num = 5;
int main()
{
    std::vector<Thread<ThreadData<int> *>> thread;
    for (int i = 0; i < num; i++)
    {
        // char* threadname = new char[64];
        // snprintf(threadname, 64, "Thread-%d", i + 1);
        std::string threadname = "thread -" + std::to_string(i + 1);
        ThreadData<int> *ptr = new ThreadData<int>(g_ticket, threadname);
        thread.emplace_back(Thread<ThreadData<int> *>(funtion, ptr, threadname));
    }
    for (auto &e : thread)
    {
        e.Start();
    }
    for (auto &e : thread)
    {
        sleep(1);
        e.Data()->Total();
        e.Join();
        delete e.Data();
    }
    return 0;

}

这里我们着重看funtion执行函数即可,在该函数外部,我定义了一把全局锁。当不同线程执行同一函数,需要访问一块共享资源(临界资源)的代码,我们就称为临界区,其它部分,我们称为非临界区。而我们要保护的,其实就是临界区的资源,这就要求我们在加锁时,尽量只要对临界区的部分进行加锁即可,对其他非临界区的部分,可以不用管。这里当多个线程同时访问同一变量时,就需要去竞争那一把全局锁。谁竞争到了锁,谁就能对临界资源进行访问,其余线程只能在临界区外进行等待。当然,这里不排除有的线程竞争锁的能力很强,让其他线程根本就竞争不到锁的情况,这就会造成其他进程的饥饿问题。在不同系统下,不同线程的竞争能力不同,这和锁的创建时间、os的调度算法等有管。

运行结果

这里就出现了线程4、5的竞争问题,不过这里,我们暂不探究。而票数这个全局变量,在加锁后,就回复正常了。在C++中,对互斥锁的释放和初始化等等操作进行了包装,这里不一一介绍,如果有需要,也可以自行封装。

3、互斥锁原理的简单介绍

互斥锁底层在不同OS下可能有不同的实现方式,这里简单介绍一种。在互斥锁中,实际上表示持有锁的状态就是用一个整数,我们可以用+或-来改变其状态,但我们前面提到,++操作并不是原子性的,所以这个用整数来表示持有锁的状态是有一定问题的。为了解决该问题,系统中有特定的汇编指令,能够原子地交换cpu寄存器和物理内存中的数值。我们用1表示持有锁,0表示不持有锁。

假设我们现在定义了一把锁,我们lock这个整型变量来表示锁地使用情况。如果lock为0,表示锁被使用,不为零,就被没使用。在内存中的数据大部分是被线程共享的,而在cpu上的寄存器中存储的硬件上下文是被线程私有的。

首先,线程内部也有整型变量表示是否持有锁,我们以0表示不持有锁,1表示持有锁。以上图为例,thread-1申请锁时,会首先将线程内部的变量读入寄存器中,然后通过特殊汇编指令与内存中的lock值交换(该过程为原子的),此时就完成了锁的申请。此时时间片耗尽,线程就会切换,同时寄存器中的硬件上下文也会被清空,锁被带走。第二个线程也会将自己表示持有锁状态的变量读入寄存器中,然后重复上述的动作,但是由于lock变量已经为零,所以第二个线程即使交换完后,也是无法持有锁的。解锁,就是把线程上的数据交换会内存中,表现在图上就是线程中的“1”换回内存中lock变量。所以其他线程想要访问临界资源,就只能等待线程把锁释放,访问临界区的过程也就是线程安全的。

以上就是所有的内容,文中如有不对之处,还望各位大佬指正,谢谢!!!

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

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

相关文章

harmonyOS 原来构建还有这么多弯弯绕绕

随着用户需求的不断增长&#xff0c;我们的 APP 已发展成功能丰富的超级APP&#xff0c;这也导致打包构建变得非常耗时&#xff0c;可能需要数小时&#xff0c;严重影响开发效率和产品迭代。通过采用模块化设计、增量构建、并行处理、缓存机制、优化依赖管理&#xff0c;以及云…

SSM整合步骤

目录 一、Mybatis整合Spring 1、整合后的maven坐标 2、核心配置文件 3、pojo、mapper、service配置 4、单测 二、整合SpringMVC 1、引入springMVC的坐标并配置tomcat 2、核心配置文件 3、controller配置 4、启动项目并测试 SSM SpringMVC Spring Mybatis 整合顺序&#xff1…

Spring AOP - 注解方式实现

前文已经讨论了基于配置文件方式实现Spring AOP&#xff08;Spring AOP - 配置文件方式实现&#xff09;&#xff0c;本文采用注解的方式实现前文相同的功能。配置步骤如下&#xff1a; 1、项目增加aop依赖&#xff08;pom.xml) <dependency><groupId>org.springfr…

大数据:快速入门Scala+Flink

一、什么是Scala Scala 是一种多范式编程语言&#xff0c;它结合了面向对象编程和函数式编程的特性。Scala 这个名字是“可扩展语言”&#xff08;Scalable Language&#xff09;的缩写&#xff0c;意味着它被设计为能够适应不同规模的项目&#xff0c;从小型脚本到大型分布式…

vue2 中使用 Tinymce 富文本编辑器详解

vue2.x使用Tinymce富文本 项目中Tinymce效果图安装依赖包/创建依赖文件创建skins文件夹汉化文件-zh_CN.js 封装组件Tinymce.vue组件中使用封装组件tinymce.vueTinymce 扩展插件集合 项目中Tinymce效果图 如果想先了解一下&#xff0c;可以浏览一博主整理的的TinyMCE中文文档&am…

【揭秘大脑与AI的鸿沟:电化学信号与非线性动态交互的奥秘】

目录 【揭秘大脑与AI的鸿沟:电化学信号与非线性动态交互的奥秘】 1. 信息传递的奇迹:电化学信号的奥秘 2. 非线性动态交互:大脑的智慧之源 3. 结构与功能的鸿沟:从并行分布到有限层次 结语:探索未知的边界 【揭秘大脑与AI的鸿沟:电化学信号与非线性动态交互的奥秘】…

【深度学习】【TensorRT】【C++】模型转化、环境搭建以及模型部署的详细教程

【深度学习】【TensorRT】【C】模型转化、环境搭建以及模型部署的详细教程 提示:博主取舍了很多大佬的博文并亲测有效,分享笔记邀大家共同学习讨论 文章目录 【深度学习】【TensorRT】【C】模型转化、环境搭建以及模型部署的详细教程前言模型转换--pytorch转engineWindows平台搭…

[C#]winform 使用opencvsharp实现玉米粒计数

【算法介绍】 这段代码是使用OpenCvSharp库&#xff08;OpenCV的C#封装&#xff09;对图像进行处理&#xff0c;主要流程包括图像的二值化、腐蚀操作、距离变换、轮廓检测&#xff0c;并在原图上标出检测到的轮廓位置及数量。下面是对代码的详细解读&#xff1a; 初始化&…

网络通信——路由器、交换机、集线器(HUB)

注意&#xff1a;传输层&#xff0c;应用层没有网路设备 一.路由器&#xff08;网络层设备&#xff09; 1.分割广播域 2.一个接口就是一个广播域 3.一般接口位4&#xff0c;8&#xff0c;12。 4.数据转发 &#xff08;由路由表转发数据&#xff09; 5.根据路由表来进行路径选…

基于微信小程序的美食外卖管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码 精品专栏&#xff1a;Java精选实战项目…

面试速通宝典——1

1. 内存有哪几种类型&#xff1f; ‌‌‌‌  内存分为五个区&#xff0c;堆&#xff08;malloc&#xff09;、栈&#xff08;如局部变量、函数参数&#xff09;、程序代码区&#xff08;存放二进制代码&#xff09;、全局/静态存储区&#xff08;全局变量、static变量&#…

米壳AI:自媒体如何获取高清原画质!真8K视频是这样下载的!

作为一名新手自媒体博主&#xff0c;你是不是也在各种短视频平台上疯狂搜索保存外网视频的方法和软件呢&#xff1f;&#x1f603;然而&#xff0c;真正能下载真 4K 视频的却寥寥无几。 别苦恼啦&#xff01;今天我就来给大家分享一个小编亲测过后真实好用的工具 —— 米壳 AI。…

网页通知设计灵感:CSS 和 JS 的 8 大创意实现

文章目录 前言正文1.霓虹灯风格的通知系统2.垂直时间轴通知3.动画徽章通知4.项目式通知5.多种状态通知&#xff1a;成功、错误、警告6.信息、警告、提示组件7.扁平化风格通知8.社交媒体风格弹出通知 总结 前言 网页通知如今已成为电商、社交平台等网站的常见功能&#xff0c;它…

Pandas -----------------------基础知识(二)

dataframe读写数据操作 import pandas as pd# 准备数据(字典) data [[1, 张三, 1999-3-10, 18],[2, 李四, 2002-3-10, 15],[3, 王五, 1990-3-10, 33],[4, 隔壁老王, 1983-3-10, 40] ]df pd.DataFrame(data, columns[id, name, birthday, age]) df写到csv文件中 &#xff0c;…

SOLIDWORKS 2025 重点新功能大放送(壹)

SOLIDWORKS 2025涵盖全新以用户为中心的增强功能&#xff0c;致力实现更智能、更快速地与团队和外部合作伙伴协同工作。 小索是设计部负责人&#xff0c;SOLIDWORKS资深使用者&#xff0c;使用SOLIDWORKS软件多年&#xff0c;喜欢分享&#xff0c;正在体验SOLIDWORKS 2025版本…

tensorboard展示不同运行的曲线结果

运行tensorboard曲线如下&#xff1a; tensorboard --logdir .有时候&#xff0c;曲线图会展示多条曲线&#xff0c;以至于我们想分辨哪条线来自哪次训练都做不到了。如下图是设置smoothing-0.6的结果&#xff1a; smoothing可以在页面找到设置按钮&#xff0c;呼出设置侧边…

【算法笔记】二分查找 红蓝染色法

目录 二分查找 红蓝染色法&#xff08;感谢灵神&#xff09;闭区间[left, right]左闭右开区间[left, right)开区间(left, right)变式 二分查找 红蓝染色法&#xff08;感谢灵神&#xff09; 这里是灵神的教学视频&#xff1a;二分查找 红蓝染色法_哔哩哔哩_ bilibili 学了二分…

玩转RabbitMQ声明队列交换机、消息转换器

♥️作者&#xff1a;小宋1021 &#x1f935;‍♂️个人主页&#xff1a;小宋1021主页 ♥️坚持分析平时学习到的项目以及学习到的软件开发知识&#xff0c;和大家一起努力呀&#xff01;&#xff01;&#xff01; &#x1f388;&#x1f388;加油&#xff01; 加油&#xff01…

中兴交换机三层配置

中兴交换机三层配置 目的&#xff1a;将1-10端口划分到3001vlan&#xff0c;11-20端口划分到3002vlan中去 客户端客户端IPvlan网关主机A88.88.1.1203001192.168.1.254主机B192.168.100.1303002192.168.100.254 1、通过Console线登录设备 **********************************…

导出导入Oracle数据库使用黑框命令方式exp、imp【亲测】

下载工具 根据自己数据库的版本下载&#xff0c;以v19为例&#xff1a; 下载基础包Basic Package和工具包Tools Package 两个压缩包中的文件夹一样&#xff0c;但内容不一样&#xff0c;将两个压缩包中的文件解压合并到一起 https://www.oracle.com/database/technologies/inst…