Linux - 线程互斥和互斥锁

news2024/11/26 0:39:19

文章目录

  • 前言
  • 一、为什么要线程互斥
    • 原子性
  • 二、互斥锁
    • 互斥锁的创建与销毁
    • 互斥锁进行互斥


前言

前几节课,我们学习了多线程的基础概念,这节课,我们来对线程互斥和互斥锁的内容进行学习。


一、为什么要线程互斥

首先我们要明白,对于多线程,其实就是多个执行流在同时执行各自的代码。 而有的时候,我们多个执行流可能会同时访问到同一份资源,我们称这种资源叫做临界资源,而我们各个执行流访问这些临界资源的代码,就叫做临界区

当我们多个执行流访问临界资源时,就可能由于OS的线程时间调度问题,导致临界资源出现紊乱问题,所以,对于这种情况,我们就需要线程互斥来保护临界资源。

示例代码

#include <iostream>
#include <unistd.h>
#include <cstdio>
#include <pthread.h>
#include <string>
#define TNUM 5

int ticket = 10000;

void *grab(void *args)
{
    const std::string name = (char *)args;

    while (1)
    {
    	// --- 临界区
        if (ticket > 0)
        {
            usleep(1000);
            printf("%s : sells ticket:%d\n", name.c_str(), ticket);
            ticket--;
        // --- 临界区
        }
        else
        {
            break;
        }
    }
    return nullptr;
}

int main()
{
    pthread_t tid[TNUM];

    for (int i = 0; i < TNUM; i++)
    {
        char name[64];
        snprintf(name, sizeof name, "new thread %d", i + 1);
        pthread_create(tid + i, nullptr, grab, (void *)name);
        usleep(10000);
    }

    for (int i = 0; i < TNUM; i++)
    {
        pthread_join(tid[i], nullptr);
    }
    return 0;
}

如同此代码,这是一个抢票系统的简易代码,五个线程都执行抢票代码,对全局变量ticket进行–操作。

这串代码看上去似乎没有问题,但是在多线程的情况下,就可能会导致问题。
在这里插入图片描述
可是为什么呢? 我们的if判断不是如果ticket<0就break吗?

这是因为,我们的–操作在汇编角度,其实是三条语句。

第一步是将内存中的ticket move 到 寄存器中
第二步才是进行计算操作
第三步是将新计算的ticket move 到内存中

而操作系统在进行线程调度的时候,可不是管你是进行到第一步,可能你才刚执行完第一步,你的时间片就到了,然后你就被操作系统丢到运行队列末尾了,这就可能会导致临界资源不正常。

原子性

解答这个问题,就需要提出一个概念。叫做原子性。
原子性就是 我们要么不做,要么就把这件事做完,而通常而言,我们可以理解为一条汇编就是原子性的。

二、互斥锁

对于多线程库pthread,也必然会考虑到上面这种问题,所以就设计了互斥锁来保护我们的临界资源!

man 3 pthread_mutex_destroy
man 3 pthread_lock
man 3 pthread_unlock

互斥锁的创建与销毁

man 3 pthread_mutex_init
int pthread_mutex_destroy(pthread_mutex_t *mutex);
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

pthread_mutex_t 就是pthread库给我们提供的锁的数据结构。

参数pthread_mutex_t* restrict_mutex 是我们需要初始化的锁。
参数const pthread_mutexattr_t *restrict attr 我们这里不做考虑,设为nullptr即可。

需要注意的是 对于mutex互斥锁的创建和初始化有两种。

第一种是对于静态或全局的pthread_mutex_t ,我们可以使用 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 这种方式直接进行初始化,而不需要调用pthread_mutex_init函数,也不需要再调用 pthread_mutex_destroy进行销毁

第二种是对于局部的pthread_mutex_t ,我们就必须要调用 pthread_mutex_init来进行初始化,最后再调用 pthread_mutex_destroy进行销毁。

互斥锁进行互斥

man 3 pthread_mutex_lock
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

将mutex 理解为一把锁,且这把锁只有一把钥匙

pthread_mutex_lock函数是申请锁的钥匙,申请到了锁的钥匙则可以继续向下执行,如果没有申请到,则挂起等待。

pthread_mutex_unlock函数就是归还钥匙。

在这里插入图片描述
这是lock的伪代码,意思就是将0放入到寄存器%al中,然后%al寄存器中的数据与内存中的mutex数据交换,本质就是共享<->私有的过程,将唯一锁变成私有的。

需要注意的是,在汇编中exchan是一条汇编,代表这是原子性的,也就保证了锁的安全性。
在这里插入图片描述
这是unlock的伪代码,将1存入到内存中的mutex中。

#include <iostream>
#include <unistd.h>
#include <cstdio>
#include <pthread.h>
#include <string>
#define TNUM 5

int ticket = 10000;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void *grab(void *args)
{
    const std::string name = (char *)args;

    while (1)
    {
        pthread_mutex_lock(&mutex);
        // --- 临界区
        if (ticket > 0)
        {
            usleep(1000);

            printf("%s : sells ticket:%d\n", name.c_str(), ticket);
            ticket--;
        // --- 临界区
        pthread_mutex_unlock(&mutex);
        }
        else
        {
            pthread_mutex_unlock(&mutex);
            break;
        }
        usleep(1000);//抢完票的后续动作
    }
    return nullptr;
}

int main()
{
    pthread_t tid[TNUM];

    for (int i = 0; i < TNUM; i++)
    {
        char name[64];
        snprintf(name, sizeof name, "new thread %d", i + 1);
        pthread_create(tid + i, nullptr, grab, (void *)name);
        usleep(10000);
    }

    for (int i = 0; i < TNUM; i++)
    {
        pthread_join(tid[i], nullptr);
    }
    return 0;
}

在这里插入图片描述

加入了互斥锁之后,结果就没有问题了。


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

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

相关文章

openGauss学习笔记-244 openGauss性能调优-SQL调优-典型SQL调优点-统计信息调优

文章目录 openGauss学习笔记-244 openGauss性能调优-SQL调优-典型SQL调优点-统计信息调优244.1 统计信息调优244.1.1 统计信息调优介绍244.1.2 实例分析&#xff1a;未收集统计信息导致查询性能差 openGauss学习笔记-244 openGauss性能调优-SQL调优-典型SQL调优点-统计信息调优…

JVM学习-底层字节码的执行过程

目录 1.一个简单的程序分析 2. a&#xff0c;a&#xff0c;a--在JVM中的执行过程 3. 一个好玩的xx 4.方法调用的字节码分析、多态的实现、对象头 5. try-catch-finally的字节码分析 5.1 try-catch 5.2 try-catch-finally 5.3特殊情况 5.3.1 try和finally块中都出现了re…

第18节 动态规划一讲

1假设有排成一行的N个位置记为1~N&#xff0c;N一定大于或等于2 开始时机器人在其中的M位置上(M一定是1~N中的一个) 如果机器人来到1位置&#xff0c;那么下一步只能往右来到2位置&#xff1b; 如果机器人来到N位置&#xff0c;那么下一步只能往左来到N-1位置&#xff1b; 如果…

GiT: Towards Generalist Vision Transformer through Universal Language Interface

GiT: Towards Generalist Vision Transformer through Universal Language Interface 相关链接&#xff1a;arxiv github 关键字&#xff1a;Generalist Vision Transformer (GiT)、Universal Language Interface、Multi-task Learning、Zero-shot Transfer、Transformer 摘要 …

BigDecimal保留两位小数失败问题

文章目录 背景问题解决如何测试代码 背景 测试时发现在线swagger测试会自动处理BigDecimal小数点后面的数字&#xff0c;就是有零的会都给你去掉&#xff0c;比如9.000与9.500到最后都会被swagger处理成9跟9.5。使用postman测是最准的&#xff0c;测出来的就是9.000跟9.500。 …

Rocky Linux 基本工具的安装

1.系统安装后先查看ip地址 ip addr 2.安装net工具 &#xff1a;ifconfig yum install net-tools 3.安装gcc &#xff1b;选择都选 y yum install gcc yum install gcc-c 4.安装tcl yum install -y tcl 5.安装lsof &#xff08;端口查看工具&#xff09; yum install l…

MySQL实现事务隔离的秘诀之锁

在MySQL中&#xff0c;有多种锁类型&#xff0c;我们先了解三种概念的锁&#xff0c;以便对接下来的内容有更好理解。 表级锁&#xff08;Table Lock&#xff09;&#xff1a;对整个表加锁&#xff0c;其他事务无法修改或读取该表的数据&#xff0c;但可以对其他表进行操作。页…

SLAM 算法综述

LiDAR SLAM 其主要思想是通过两个算法&#xff1a;一个高频激光里程计进行低精度的运动估计&#xff0c;即使用激光雷达做里程计计算两次扫描之间的位姿变换&#xff1b;另一个是执行低频但是高精度的建图与校正里程计&#xff0c;利用多次扫描的结果构建地图&#xff0c;细化位…

切面条-蓝桥杯?-Lua 中文代码解题第1题

切面条-蓝桥杯&#xff1f;-Lua 中文代码解题第1题 一根高筋拉面&#xff0c;中间切一刀&#xff0c;可以得到2根面条。 如果先对折1次&#xff0c;中间切一刀&#xff0c;可以得到3根面条。 如果连续对折2次&#xff0c;中间切一刀&#xff0c;可以得到5根面条。 那么&#xf…

【QT入门】VS2019+QT的开发环境配置

声明&#xff1a;该专栏为本人学习Qt知识点时候的笔记汇总&#xff0c;希望能给初学的朋友们一点帮助(加油&#xff01;) 往期回顾&#xff1a; 【QT入门】什么是qt&#xff0c;发展历史&#xff0c;特征&#xff0c;应用&#xff0c;QtCreator-CSDN博客【QT入门】Windows平台下…

Java设计模式 | 设计模式概述和分类

独孤求败五重境界 利剑&#xff08;“凌厉刚猛&#xff0c;无坚不摧&#xff0c;弱冠前以之与河朔群雄争锋。”&#xff09;软剑&#xff08;“紫薇软剑&#xff0c;三十岁前所用&#xff0c;误伤义士不祥&#xff0c;乃弃之深谷。”&#xff09;重剑&#xff08;“重剑无锋&a…

React 实现下拉刷新效果

简介 本文基于react实现下拉刷新效果&#xff0c;在下拉的时候会进入loading状态。 实现效果 效果如上图所示&#xff0c;在下拉到底部时候&#xff0c;会出现loading条&#xff0c;在处理完成后loading条消失。 具体代码 布局 & 逻辑 import {useRef, useState} from …

【IC设计】Verilog线性序列机点灯案例(二)(小梅哥课程)

文章目录 该系列目录&#xff1a;设计目标设计思路RTL 及 Testbench仿真结果存在的问题&#xff1f;改善后的代码RTL代码testbench代码 仿真结果 案例和代码来自小梅哥课程&#xff0c;本人仅对知识点做做笔记&#xff0c;如有学习需要请支持官方正版。 该系列目录&#xff1a;…

javaEE——线程的等待和结束

文章目录 Thread 类及常见方法启动一个线程中断一个线程变量型中断调用 interrupt() 方法来通知观察标志位是否被清除 等待一个线程获取当前线程引用休眠当前线程 线程的状态观察线程的所有状态观察 1: 关注 NEW 、 RUNNABLE 、 TERMINATED 状态的切换 多线程带来的风险为什么会…

Ubuntu 14.04:安装 PaddleOCR 2.3

目录 一、说明 1.1 如何选择版本 1.2 查看 github 中的 PaddleOCR 版本 二、安装 2.1 安装前环境准备 2.2 下载包 2.3 解压 2.4 安装依赖库 异常处理&#xff1a;Read timed out. 2.5 下载推理模型&#xff1a;inference 2.5.1 模型存放位置 2.5.2 模型下载链接 2.5.…

【5G NB-IoT NTN】3GPP R17 NB-IoT NTN介绍

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

​​SQLiteC/C++接口详细介绍之sqlite3类(十)

返回目录&#xff1a;SQLite—免费开源数据库系列文章目录 上一篇&#xff1a;SQLiteC/C接口详细介绍之sqlite3类&#xff08;九&#xff09; 下一篇&#xff1a;​​SQLiteC/C接口详细介绍之sqlite3类&#xff08;十一&#xff09; 30.sqlite3_enable_load_extension&#x…

Docker 学习笔记一

一、什么是docker Docker 是一个基于轻量级虚拟化技术的容器&#xff0c;整个项目基于Go语言开发&#xff1b;Docker是一个C/S架构&#xff0c;后端众多模块各司其职&#xff0c;docker的daemon是运行在主机上通过client可以进行通信。 docker 由三部分组成&#xff1a;镜像(…

读算法的陷阱:超级平台、算法垄断与场景欺骗笔记12_移动平台(上)

1. 广告 1.1. 广告收入的来源 1.1.1. 向客户推荐广告投放网址 1.1.2. 提供有效提高产品广告点击率的咨询服务 1.1.3. 从合作伙伴的广告收入中捞上一笔 1.2. 对于广告主来讲&#xff0c;他们无意于与各家网站逐一谈判 1.2.1. 这种方式一是成本过高&#xff0c;二是费时费力…

C#控制台贪吃蛇

Console.Write("");// 第一次生成食物位置 // 随机生成一个食物的位置 // 食物生成完成后判断食物生成的位置与现在的蛇的身体或者障碍物有冲突 // 食物的位置与蛇的身体或者障碍物冲突了&#xff0c;那么一直重新生成食物&#xff0c;直到生成不冲突…