[ Linux ] 互斥量实现原理,线程安全

news2025/2/6 13:13:07

上篇文章我们对抢票系统做了加锁处理,对互斥量tickets进行加锁。而本篇博文来谈谈互斥量实现的原理以及相关问题。

目录

1.上篇遗留问题

我们在临界资源对应的临界区中加锁了,就不是多行代码了吗?如果还是多行代码,难道不会被切换走吗?

2.如何实现线程加锁和解锁的原子性

3.可重入和线程安全

3.1概念

3.2常见的线程安全的情况

3.3常见的线程安全的情况

3.4常见不可重入的情况

3.5常见可重入的情况

3.6可重入与线程安全联系

3.7可重入与线程安全区别


1.上篇遗留问题

我们在临界资源对应的临界区中加锁了,就不是多行代码了吗?如果还是多行代码,难道不会被切换走吗?

在讨论这个问题之前我们先复习一下昨天的代码:

#include <iostream>
#include <unistd.h>
#include <cstdlib>
#include <pthread.h>

using namespace std;

int tickets = 1000;

//全局初始化
//pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

pthread_mutex_t mutex;//锁的声明

void *startRoutine(void *args)
{
    const char* name = static_cast<const char *>(args);

    while(true)
    {
        pthread_mutex_lock(&mutex);//如果申请不到就会阻塞线程
        if(tickets > 0)
        {
            usleep(1000);
            cout<<name<<" 抢到了一张票,票的编号是...."<< tickets << endl;
            tickets--;
            pthread_mutex_unlock(&mutex);
        }
        else
        {
            cout<<name<<" 没有抢到票,没有票了............"<< tickets << endl;
            pthread_mutex_unlock(&mutex);
            break;
        }
    }
    return nullptr;
}

int main()
{
    pthread_mutex_init(&mutex,nullptr);
    pthread_t tid1,tid2,tid3,tid4;

    pthread_create(&tid1,nullptr,startRoutine,(void*)"thread 1");
    pthread_create(&tid2,nullptr,startRoutine,(void*)"thread 2");
    pthread_create(&tid3,nullptr,startRoutine,(void*)"thread 3");
    pthread_create(&tid4,nullptr,startRoutine,(void*)"thread 4");



    pthread_join(tid1,nullptr);
    pthread_join(tid2,nullptr);
    pthread_join(tid3,nullptr);
    pthread_join(tid4,nullptr);

    pthread_mutex_destroy(&mutex);



    return 0;
}

我们发现这是我们的加锁后抢票的代码,我们也可以在主线程内定义锁,然后通过结构体传入新线程内,具体的玩法如下代码所示:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <cstdlib>
#include <pthread.h>

using namespace std;

int tickets = 10000;

//全局初始化
//pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

// pthread_mutex_t mutex;//锁的声明
#define NAMESIZE 64
typedef struct threadData
{
    char name[NAMESIZE];
    pthread_mutex_t *mutexp;//声明锁
}threadData;

void *startRoutine(void *args)
{
    //const char* name = static_cast<const char *>(args);
    threadData *td = static_cast<threadData*>(args);
    while(true)
    {
        pthread_mutex_lock(td->mutexp);//如果申请不到就会阻塞线程
        if(tickets > 0)
        {
            usleep(1000);
            cout<<td->name<<" 抢到了一张票,票的编号是...."<< tickets << endl;
            tickets--;
            pthread_mutex_unlock(td->mutexp);
            usleep(200);
        }
        else
        {
            cout<<td->name<<" 没有抢到票,没有票了............"<< tickets << endl;
            pthread_mutex_unlock(td->mutexp);
            break;
        }
    }
    return nullptr;
}

int main()
{
    //pthread_mutex_init(&mutex,nullptr);
    static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    pthread_t tid1,tid2,tid3,tid4;

    threadData *td1 = new threadData();
    strcpy(td1->name,"thread 1");
    td1->mutexp = &mutex;
    pthread_create(&tid1,nullptr,startRoutine,td1);

    threadData *td2 = new threadData();
    strcpy(td2->name,"thread 2");
    td2->mutexp = &mutex;
    pthread_create(&tid2,nullptr,startRoutine,td2);

    threadData *td3 = new threadData();
    strcpy(td3->name,"thread 3");
    td3->mutexp = &mutex;
    pthread_create(&tid3,nullptr,startRoutine,td3);

    threadData *td4 = new threadData();
    strcpy(td4->name,"thread 4");
    td4->mutexp = &mutex;
    pthread_create(&tid4,nullptr,startRoutine,td4);



    pthread_join(tid1,nullptr);
    pthread_join(tid2,nullptr);
    pthread_join(tid3,nullptr);
    pthread_join(tid4,nullptr);

    //pthread_mutex_destroy(&mutex);



    return 0;
}

那么我们在临界资源对应的临界区中加锁了,就不是多行代码了吗?如果还是多行代码,难道不会被切换走吗?

我们在临界区内可以被切走吗?

答案是当然可以。因为线程执行的加锁解锁也是代码,线程在任意代码出都可以被切换,但是线程加锁是原子的,要么拿到锁要么拿不到锁。但是由于该线程占用着锁,即使在临界区内被切换走,其他线程也不可能访问临界区,因为他们根本拿不到锁,锁已经被刚才的线程占用了。因此该线程会被阻塞在申请锁的阶段。因此一个线程持有锁,该线程根本不担心任何的切换问题。对于其他线程而言,该线程访问临界区 只有没有进入和使用完毕两种状态对其他线程有意义。

2.如何实现线程加锁和解锁的原子性

经过上面的理解,我们已经可以意识到单纯的++i和i++都不是原子的,有可能会有数据一致性问题。

为了实现互斥锁操作,大多数体系结构都提供了swap或者exchange指令,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理平台,访问内存的总线周期也有先有后,一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。

凡是在寄存器中的数据,全部都是线程的内部上下文!!多个线程看起来同时在访问寄存器,但是互不印象。

本质:将数据从内存读入寄存器,本质是将数据从共享变成线程私有!由于是交换而不是拷贝,因此这个变量只有一份,就犹如一个令牌,谁拿到这个令牌就相当于谁持有锁!而被该线程私有!

3.可重入和线程安全

3.1概念

  • 线程安全:多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现该问题。
  • 重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则,是不可重入函数。

3.2常见的线程安全的情况

  • 不保护共享变量的函数
  • 函数状态随着被调用,状态发生变化的函数
  • 返回指向静态变量指针的函数
  • 调用线程不安全函数的函数

3.3常见的线程安全的情况

  • 每个线程对全局变量或者静态变量只有读取的权限,没有写入的权限,一般来说这些线程是安全的
  • 类或者接口对于线程来说都是原子操作
  • 多个线程之间的切换不会导致该接口的执行结果存在二义性

3.4常见不可重入的情况

  • 调用了malloc/free函数,因为malloc函数使用全局链表来管理堆的
  • 调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构
  • 可重入函数体内使用了静态的数据结构

3.5常见可重入的情况

  • 不使用全局变量或静态变量
  • 不使用用malloc或者new开辟出的空间
  • 不调用不可重入函数
  • 不返回静态或全局数据,所有数据都有函数的调用者提供
  • 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据

3.6可重入与线程安全联系

  • 函数是可重入的,那就是线程安全的
  • 函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题
  • 如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的。

3.7可重入与线程安全区别

  • 可重入函数是线程安全函数的一种
  • 线程安全不一定是可重入的,而可重入函数则一定是线程安全的。
  • 如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生死锁,因此是不可重入的

(本篇完)

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

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

相关文章

[附源码]计算机毕业设计Python公益组织登记与查询系统论文(程序+源码+LW文档)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

【AI with ML】第 13 章 :在 Android 应用程序中使用 TensorFlow Lite

&#x1f50e;大家好&#xff0c;我是Sonhhxg_柒&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f4dd;个人主页&#xff0d;Sonhhxg_柒的博客_CSDN博客 &#x1f4c3; &#x1f381;欢迎各位→点赞…

vue的虚拟DOM和diff算法

虚拟DOM和diff算法密不可分&#xff0c; 虚拟dom&#xff0c;它本身就是一个 JavaScript 对象&#xff0c;为解决DOM操作非常耗时&#xff0c;把DOM转换为虚拟DOM&#xff0c;DOM操作转换为js计算&#xff0c;js执行速度较快。 diff算法在vue中被优化为O(n)的时间复杂度&#x…

手机损坏的数据导出方法

主板损坏的症状 手机主板损坏的症状表现一般为系统启动失败、屏幕无显示、启动黑屏死机、无法开机、没有声音、无法打电话、没网络等等。 资料的重要性 手机坏了,怎么把手机里面重要的资料导出来?今天小编就来跟大家说说手机导资料这个事。 手机坏了,具体要看手机是哪里…

java计算机毕业设计ssm基于JavaWeb的公务员招考信息发布平台

项目介绍 随着互联网技术的发发展,计算机技术广泛应用在人们的生活中,逐渐成为日常工作、生活不可或缺的工具,各种管理系统层出不穷。公务员是各国负责统筹管理经济社会秩序和国家公共资源,维护国家法律规定,贯彻执行相关义务的公职人员。每年报考公务员的人数逐渐增加,特为此…

寒假训练营 第一节 时空复杂度分析

一.时空复杂度的分析 1.时间复杂度 时间复杂度&#xff1a;程序运行会消耗多少时间。 在此&#xff0c;为了方便讨论&#xff0c;我们假定每一个语句消耗的时间一样&#xff0c;视为一个时间单元。 #include <iostream> using namespace std; int main() {int n 100;…

国企的设备管理工作如何做好?

国企的设备管理工作如何做好&#xff1f; 在市场经济建设持续深化、经营体制不断转变的过程中&#xff0c;一些企业设备管理工作发生了很大的变化&#xff0c;出现整体水平下降&#xff0c;设备技术状况恶化等现象&#xff0c;这在一定程度上制约了企业技术创新&#xff0c;制…

Transformer实现以及Pytorch源码解读(二)-embedding源码分析

前言 本部分博客需要先阅读博客&#xff1a;《Transformer实现以及Pytorch源码解读&#xff08;一&#xff09;-数据输入篇》 作为知识储备。 Embedding使用方式 如下面的代码中所示&#xff0c;embedding一般是先实例化nn.Embedding(vocab_size, embedding_dim)。实例化的过…

业务安全情报,预知发现黑灰产的企业攻击

业务遭遇欺诈风险&#xff0c;发起攻击的黑灰产主要是为了谋取利益。对于黑灰产利益目的甄别需要多方面情报&#xff0c;再辅助技术和专家经验&#xff0c;然后进行综合判断&#xff0c;进而帮助企业及时响应、精准布控。 安全情报帮助企业提前发现攻击 12月13日&#xff0c;“…

谁代表先进生产力?

互联网企业代表先进生产力方向 做软件项目的时候碰到三类企业 1、 传统企业&#xff0c;以卖货或卖服务为主 2、 互联网类&#xff0c;做个服务工具或平台 3、 分配模式类&#xff0c;以某分配模式为“宝贝” 毫无疑问&#xff1a; 互联网企业代表先进的生产力方向 互联网类…

即时通讯音视频开发之音频基础及编码原理

即时通讯应用中的实时音视频技术&#xff0c;几乎是IM开发中的最后一道高墙。原因在于&#xff1a;实时音视频技术 音视频处理技术 网络传输技术 的横向技术应用集合体&#xff0c;而公共互联网不是为了实时通信设计的。 比特率&#xff1a; 表示经过编码&#xff08;压缩&am…

C#读取Excel文件内容(WPS)

本地安装的WPS版本为 一、下载accessdatabaseengine_X64后安装 网址&#xff1a;https://www.microsoft.com/en-us/download/details.aspx?id54920 二、项目中引用OleDb包 三、代码部分 //excelFilePath为文件路径&#xff08;例如D:\Test.xslx&#xff09; > //strin…

Android---RecyclerView回收复用机制

一、RecyclerView回收复用 回收什么&#xff1f;复用什么&#xff1f; 回收&#xff1a;回收即缓存。当屏幕上的一个itemView滑出屏幕(即不可见了)&#xff0c;RecyclerView就利用回收机制&#xff0c;将该itemView放入内存。当其它itemView出现时&#xff0c;不用每次都去new…

JavaScript-Sass

Sass的基础使用 1.简介 1.1简介 Sass是世界上最成熟&#xff0c;最稳定&#xff0c;最强大的CSS扩展语言Sass是css预编译工具可以更加优雅的书写csssass写出来的东西浏览器不认识需要进行转换VSCode推荐使用Easy Sass插件Sass中可以使用加减乘除&#xff0c;条件分支以及循环…

【Three.js入门】处理动画、尺寸自适应、双击进入/退出全屏(Clock跟踪时间,Gsap动画库,自适应画面,进入/退出全屏)

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;也会涉及到服务端 &#x1f4c3;个人状态&#xff1a; 在校大学生一枚&#xff0c;已拿多个前端 offer&#xff08;秋招&#xff09; &#x1f680;未…

Python -- 网络编程

目录 1.网络通信的概念 2.IP地址 3.网络通信方式 3.1 直接通信 3.2 使用集线通信 3.3 通用交换机通信 3.4 使用路由器连接多个网络 3.5 复杂的通信过程 4.端口 4.1 端口号 4.2 知名端口号 4.3 动态端口号 4.4 端口号作用 5.socker概念 5.1 不同电脑上的进程之间…

【二叉树经典习题讲解】

If you find a path with no obstacles, probably doesnt lead anywhere. 目录 1 前中后序遍历一颗二叉树 2 总的结点个数 3 求叶子节点个数 4 求树的高度 5 第k层结点个数 6 二叉树的层序遍历 7 判断一棵树是否为完全二叉树 1 二叉树的前序遍历 2 单值二叉树 3 翻转二…

2022卡塔尔世界杯的两个球员:一个吸螺,一个没吸

你好&#xff0c;我是YourBatman&#xff1a;一个俗人&#xff0c;贪财好色。 2022年12月18日&#xff0c;卢塞尔球场&#xff0c;太太太精彩了&#xff0c;这场世界杯决赛&#xff01;卡塔尔世界杯&#xff0c;已经离我们远去&#xff0c;阿根廷最终满载而归。 那一个个珍贵…

大脑的默认模式网络DMN

虽然默认模式网络DMN现在是rs-fMRI领域中的研究热点&#xff0c;但最初观察到默认模式网络的工具是PET&#xff0c;并且是从任务态过渡到静息态的 PET中大脑功能活动基线的定义&#xff1a; 基线是理解复杂系统的基础根据脑氧提取分数&#xff08;OEF值&#xff09;可以确定正…

前端CSS实现跳动的文字

效果图 首选来一个简单的布局 这里就不用多说&#xff0c;都是简单排版 <h1>一个爬坑的Coder</h1>html {height: 100%; }body {display: flex;justify-content: center;align-items: center;height: 100%; } h1 {font-size: 48px; }每个文字独立出来 每个文字都…