【hello Linux】线程互斥

news2024/11/19 13:44:37

目录

1. 互斥量mutex

2. 互斥量的接口

2.1 初始化互斥量

2.2 销毁互斥量

2.3 互斥量加锁和解锁

2.4 互斥量实现原理探究

3. 可重入VS线程安全

4. 常见锁概念

5. 多线程抢票系统



Linux🌷 

在介绍线程互斥前,我们先来看几个专业性术语:
【临界资源】:能被多线程共享访问,但每次只能被一个线程访问的资源(打印机);
【临界区】:访问临界资源的代码;
【互斥】:在任意时刻,只允许一个线程访问临界资源,称为互斥;
【同步】:在互斥的基础上各线程对临界资源的一个有序访问;
【原子性】: 如果把一个事务看作是一个程序,它要么完整的被执行,要么完全不执行。这
种特性就叫原子性。一条汇编语句便是原子的;

大部分情况,各线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量。

但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。

多个线程并发的操作共享变量,会带来一些问题。

比如A、B两个线程先后对一个共享变量 int a=10; 进行自增操作,则可能出现以下几种情况:

A:a = 11;B:a = 12;

A:a = 12;B:a = 12;

在此不做全部情况展示,目的是说明问题:

为什么会出现这种情况呢?

因为对 a++;并不是原子操作,它会先将内存中a的值放入CPU中,然后在CPU中进行递增操作,最后将算好的值存回内存中,三个操作如果不是一气呵成的则便会出现错误;

我们通过查看汇编代码也可以看到:a++语句需要三句汇编才能实现(一句汇编才是原子的);

为了解决上述问题,我们引入了互斥量,使得在某一时刻内只允许一个线程访问共享资源;

1. 互斥量mutex

互斥量其实就是一把锁,使得各线程对临界资源的访问都是互斥的;

2. 互斥量的接口

2.1 初始化互斥量

初始化互斥量有两种方法:
方法1,静态分配:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
方法2,动态分配:
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

参数:
    mutex:要初始化的互斥量
    attr:NULL

2.2 销毁互斥量

销毁互斥量需要注意:
  • 使用PTHREAD_ MUTEX_ INITIALIZER初始化的互斥量不需要销毁;
  • 不要销毁一个已经加锁的互斥量;
  • 已经销毁的互斥量,要确保后面不会有线程再尝试加锁;
int pthread_mutex_destroy(pthread_mutex_t *mutex);

2.3 互斥量加锁和解锁

//加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);

//解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);

返回值:
    成功返回0,失败返回错误号
调用 pthread_ lock 时,可能会遇到以下情况:
  • 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功;
  • 发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_ lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁。

2.4 互斥量实现原理探究

经过上面的例子,大家已经意识到单纯的  i++  或者  ++i  都不是原子的,可能会有数据一致性
问题;锁其实也是一个临界资源,为了实现互斥锁操作,大多数体系结构都提供了 swap
exchange 指令 ,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了
锁的原子性。即使是多处理器平台,访问内存的总线周期也有先后,一个处理器上的交换指令执
行时另一个处理器的交换指令只能等待总线周期。 

3. 可重入VS线程安全

1. 重入和线程安全的概念

  • 线程安全:多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现该问题。
  • 重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则,是不可重入函数。
2. 常见的线程不安全的情况
  • 不保护共享变量的函数;
  • 函数状态随着被调用,状态发生变化的函数;
  • 返回指向静态变量指针的函数;
  • 调用线程不安全函数的函数;
3.  常见的线程安全的情况
  • 每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的;
  • 类或者接口对于线程来说都是原子操作;
  • 多个线程之间的切换不会导致该接口的执行结果存在二义性;
4.  常见不可重入的情况
  • 调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的;
  • 调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构;
  • 可重入函数体内使用了静态的数据结构;
5.  常见可重入的情况
  • 不使用全局变量或静态变量;
  • 不使用malloc或者new开辟出的空间;
  • 不调用不可重入函数;
  • 不返回静态或全局数据,所有数据都有函数的调用者提供;
  • 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据;
6.  可重入与线程安全联系
  • 函数是可重入的,那就是线程安全的;
  • 函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题;
  • 如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的;
7.  可重入与线程安全区别
  • 可重入函数是线程安全函数的一种;
  • 线程安全不一定是可重入的,而可重入函数则一定是线程安全的;
  • 如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生死锁,因此是不可重入的;

4. 常见锁概念

1. 死锁
死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用
不会释放的资源而处于的一种永久等待状态。
2. 死锁四个必要条件
【互斥条件】:一个资源每次只能被一个执行流使用;
【请求与保持条件】:一个执行流因请求资源而阻塞时,对已获得的资源保持不放;
【不剥夺条件】 : 一个执行流已获得的资源,在末使用完之前,不能强行剥夺;
【循环等待条件】 : 若干执行流之间形成一种头尾相接的循环等待资源的关系;
3. 避免死锁
  • 破坏死锁的四个必要条件;
  • 加锁顺序一致(如有多个锁,加锁和释放锁相对应);
  • 避免锁未释放的场景;
  • 资源一次性分配;
4. 避免死锁算法
  • 死锁检测算法
  • 银行家算法

银行家算法、死锁检测算法实践

银行家算法
上述两篇博客大家都可以看看!👀
经过上述内容的学习之后,我们编写一个简单的多线程抢票系统:

5. 多线程抢票系统

  •  makefile:
tickets:tickets.cc
	g++ -o $@ $^ -std=c++11 -lpthread

.PHONY:clean
clean:
	rm -f tickets
  •  tickets.cc
#include <iostream>
#include <cstdio>
#include <string>
#include <ctime>
#include <mutex>
#include <cstdlib>
#include <unistd.h>
#include <pthread.h>

//票类
class Ticket
{
private:
    //票数
    int tickes;
    //互斥量
    pthread_mutex_t mtx;
public:
    //构造函数
    Ticket():tickes(1000)
    {
        pthread_mutex_init(&mtx,nullptr);
    }
    //抢票函数
    bool GetTicket()
    {
        bool res=true;
        //加锁
        pthread_mutex_lock(&mtx);
        //临界区
        if(tickes>0)
        {
            usleep(1000);
            std::cout<<"我是["<<pthread_self()<<"]我要抢的票是:"<<tickes<<std::endl;
            tickes--;
        }
        else
        {
            std::cout<<"票已经抢空了"<<std::endl;
            res=false;
        }
        pthread_mutex_unlock(&mtx);
        return res;
    }
    //析构函数
    ~Ticket()
    {
        pthread_mutex_destroy(&mtx);
    }
};
//线程执行的函数
void* ThreadRoutine(void* args)
{
    Ticket* t=(Ticket*)args;
    while(true)
    {
        if(!t->GetTicket())
        {
            break;
        }
    }    
}

//主函数
int main()
{
    //创建票对象
    Ticket* t = new Ticket();
    //创建线程
    pthread_t tid[5];
    for(int i=0;i<5;i++)
    {
        pthread_create(tid+i,nullptr,ThreadRoutine,(void*)t);
    }
    //等待线程
    for(int i=0;i<5;i++)
    {
        pthread_join(tid[i],nullptr);
    }
    return 0;
}
  • 运行结果:

如上便是一个简单的多线程抢票系统;

在结果中我们发现虽然创建了5个线程,但出现了一个线程连续多次抢票的情况:

这是因为这个线程比较活跃,总能申请到锁资源(其实单CPU情况下,也就是在时间片内该线程的运行情况)

如上代码是一个采用静态分配初始化互斥量,我们也可以采用动态分配初始化互斥量: 

//票类
class Ticket 
{
private:
    int tickets;
    pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
    //pthread_mutex_t mtx;
public:
    //构造函数
    Ticket():tickets(1000)
    {
        //pthread_mutex_init(&mtx,nullptr);
    }
    //抢票
    bool GetTicket()
    {
        bool res=true;
        //加锁
        pthread_mutex_lock(&mtx);
        //临界区
        if(tickets>0)
        {
            usleep(1000);
            std::cout<<"我是线程["<<pthread_self()<<"],我正在抢"<<tickets<<"号票"<<std::endl;
            tickets--;
        }
        else
        {
            std::cout<<"票抢完了"<<std::endl;
            res=false;
        }
        pthread_mutex_unlock(&mtx);
        return res;
    }
    //析构函数
    ~Ticket()
    {
        //pthread_mutex_destroy(&mtx);
    }
};

如果上述文章对您有所帮助的话,还请点赞👍,收藏😉,关注🎈

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

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

相关文章

边缘计算节点是啥?边缘计算与CDN有什么关系?一文带你了解边缘计算节点BEC

边缘计算节点是基于CDN边缘节点构建&#xff0c;覆盖全国大部分地区&#xff0c;三大运营商全覆盖。将算力下沉到各城市级节点&#xff0c;提供离用户更近的算力资源。 那么可能有些小伙伴会问&#xff0c;CDN也是就近为用户提供服务&#xff0c;边缘计算节点和CDN有什么不同呢…

时序数据利用EEMD_LSTM模型进行预测(Python编程,数据集和代码均在压缩包,解压缩后可以直接运行,数据可以替换为股票数据,交通流量等时序数据)

运行效果(为减少录屏时间&#xff0c;视频中epoch设置为30&#xff0c;改为100效果更佳):利用EEMD_LSTM模型对时序数据进行预测&#xff08;视频中epoch为30&#xff0c;当为100 的时候效果更佳&#xff09;_哔哩哔哩_bilibili 1.数据介绍&#xff1a;以每天为间隔的时序数据 …

达梦:dts工具迁移mysql decimal(65,30)的字段,报精度超出定义

本文旨在分享迁移MySQL decimal字段​​​​​​​时遇到“精度超出定义”问题时&#xff0c;如何理解MySQL和达梦对于decimal 等这一类数值数据类型。 1.了解达梦的数值数据类型定义 ​​​​​​​​​​​​​​NUMERIC 类型 语法&#xff1a;NUMERIC[(精度 [, 标度])]功…

HBase基础

HBase基础 一、初识HBase HBase 是一个面向列式存储的分布式数据库&#xff0c;其设计思想来源于 Google 的 BigTable 论文。HBase 底层存储基于 HDFS 实现&#xff0c;集群的管理基于 ZooKeeper 实现。HBase 良好的分布式架构设计为海量数据的快速存储、随机访问提供了可能&…

实验二 存储器管理

实验二 存储器管理 实验目的&#xff1a; 理解各类置换算法的原理和虚拟存储器管理的方法。 实验内容&#xff1a; 编程实现LRU算法或CLOCK/改进算法等置换算法&#xff08;二选一&#xff09;&#xff0c;模拟实现虚拟存储器的地址变换过程。 实验步骤&#xff1a; 1…

C++之虚函数原理

对象数据和函数的存储方式 注意说的是对象。 C中的对象存储方式是 每个对象占用的存储空间只是该对象的数据部分&#xff08;虚函数指针和虚基类指针也属于数据部分&#xff09;&#xff0c;函数属于公共部分。 虚函数表 虚函数是通过虚函数表实现的。 C实现虚函数的方法是…

open3d io操作

目录 1. read_image, write_image 2. read_point_cloud, write_point_cloud 3. 深度相机IO操作 4. Mesh文件读取 1. read_image, write_image 读取jpg. png. bmp等文件 image_io.py import open3d as o3dif __name__ "__main__":img_data o3d.data.JuneauIma…

Redis持久化---RDBAOF

目录 一、什么是持久化&#xff0c;为什么要持久化&#xff1f; 二、RDB 2.1 配置文件 2.2 自动触发 2.3 手动触发 2.4 RDB优缺点 2.5 如何修复dump.rdb文件 2.6 哪些情况会触发快照 && 如何禁用RDB&#xff1f; 三、AOF 3.1 什么是AOF&#xff1f; 3.2 AO…

(四)Kubernetes - 手动部署(二进制方式安装)

Kubernetes - 手动部署 [ 3 ] 1 部署work node1.1 创建工作目录并拷贝二进制文件1.2 部署kubelet1.2.1 创建配置文件1.2.2 配置文件1.2.3 生成kubelet初次加入集群引导kubeconfig文件1.2.4 systemd管理kubelet1.2.5 启动并设置开机启动1.2.6 允许kubelet证书申请并加入集群 1.3…

猫狗训练集训练报错:Failed to find data adapter that can handle input

这里写自定义目录标题 Jupyter Notebook6.5.4 tensorflow 2.12.0 pillow 9.5.0 numpy 1.23.5 keras 2.12.0 报错详细内容&#xff1a; ValueError: Failed to find data adapter that can handle input: (<class ‘tuple’> containing values of types {“<class ‘k…

Midjourney关键词分享!附输出AI绘画参考图

Midjourney 关键词是指用于 Midjourney 这个 AI 绘画工具的文本提示&#xff0c;可以影响生成图像的风格、内容、细节等。Midjourney 关键词有一些基本的语法规则和套用公式&#xff0c;也有一些常用的风格词汇和描述词汇&#xff0c;这里我以10张不同风格和类型的美女图为例&a…

windows 下Node.js 版本管理工具

目录 1、概述&#xff1a; 2、下载安装 3、nvm命令 4、如何安装不在可用列表里面的版本 1、概述&#xff1a; 不同项目使用的nodejs版本和依赖等不同&#xff0c;需要进行nodejs的版本切换&#xff0c;使用nvm可以方便的切换当前的nodejs版本 windows可以使用 nvm-window…

AP360X 可充电多功能LED手电筒与移动照明控制ic和应用方案

产品展示 线路图如下&#xff1a; ​ AP360X芯片应用原理图和扩容1.8A应用&#xff1a; ​​ 1&#xff0c;产品介绍 AP360X 系列产品是一款多种模式可选 的单芯片 LED 手电筒控制芯片&#xff0c;集成了锂电 池充电管理模块、手电筒功能控制模块和保 护模块&#xff0c;关机…

剑指 Offer 34. 二叉树中和为某一值的路径 / LeetCode 113. 路径总和 II(深度优先搜索)

题目&#xff1a; 链接&#xff1a;剑指 Offer 34. 二叉树中和为某一值的路径&#xff1b;LeetCode 113. 路径总和 II 难度&#xff1a;中等 给你二叉树的根节点 root 和一个整数目标和 targetSum &#xff0c;找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。 …

身为程序员,你有哪些提高写代码效率的黑科技?

目录 1、Google/Stackoverflow——搜索解决方案的能力 2、低代码平台——提供可复用的轮子 3、人工智能——帮你写代码 4、学会话术——消除烦恼 5、 按时上下班&#xff0c;一周工作 5 天&#xff0c;养足精神以更高效地写代码。 首先&#xff0c;每个程序员都是会利用工…

GPU理解

什么是GPU GPU(Graphics Processing Unit)代表图形处理单元。该术语通常与图形卡或视频卡等术语互换使用。从技术上讲&#xff0c;GPU 是第三方显卡或主板上的主要图形处理芯片。 GPU 与 CPU不同。CPU 是中央处理器&#xff0c;它是计算机的主要大脑。GPU 专用于执行在计算机…

操作系统内存管理笔记

计算机的硬件设备 计算机的硬件设备中&#xff0c;有三个部件最为关键&#xff0c;它们分别是中央处理器CPU、内存和I/O控制芯片。 系统软件 系统软件可以分成两块&#xff0c;一块是平台性的&#xff0c;比如操作系统内核、驱动程序、运行库和数以千计的系统工具&#xff1…

文献阅读(51)—— Transformer 用于中国空气质量检测

文献阅读&#xff08;51&#xff09;—— Transformer 用于中国空气质量检测 文章目录 文献阅读&#xff08;51&#xff09;—— Transformer 用于中国空气质量检测先验知识/知识拓展文章结构背景文章方法1. Dartboard Spatial MSA(DS-MSA)2. CT-MSA3. 自上而下的随机阶段 文章…

Tapdata 的 ∞ 实践:中小企业如何轻量、高效地搭建起一个灵活易用的数字化平台

数字化浪潮的裹挟下&#xff0c;企业的转型之路正在变得愈加清晰。 然而在数字化转型这条企业生存和发展的必由之路上&#xff0c;更易受到市场变化冲击、所处环境竞争压力更大的中小企业无疑在面临更多的困难和挑战。一方面&#xff0c;中小企业为了顺应时代潮流、适应市场需…

jQuery移动端日期组件,H5移动端日期组件,MUI移动端日期组件,移动端简单的日期组件

前言 比较简单 H5移动端日期组件&#xff0c;使用的是MUI官方JS组件&#xff0c;因为不想自己写一个所以直接拿来改动一下用了 效果图 实现 准备工作 到官网下载css和js&#xff1a;https://dev.dcloud.net.cn/mui/ 到官网查看API&#xff1a;https://dev.dcloud.net.cn/mu…