Linux - 线程互斥和互斥锁

文章目录

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


前言

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


一、为什么要线程互斥

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

当我们多个执行流访问临界资源时,就可能由于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;
}

在这里插入图片描述

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


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AOP切面编程

1.基本概念&#xff1a; AOP&#xff08;Aspect-Oriented Programming&#xff0c;面向切面编程&#xff09;&#xff0c;跟oop面向过程编程相对&#xff0c;AOP一般用于将公共逻辑和业务逻辑进行拆分&#xff0c;可以减少代码间的耦合性。 有道翻译&#xff1a;AOP—面向切面…

jsp2024.3.21(4) html基础

一、实验目的 1、html 文件的基本结构&#xff1b; 2、html 的常用标记&#xff1b; <HTML> <HEAD> …… </HEAD> <BODY> …… </BODY> </HTML> 二、实验项目内容&#xff08;实验题目&#xff09; HTML常用标记 1&#xff0e;<…

C语言数据在内存中的存续:一篇文章让你秒懂基础!

JAMES别扣了-CSDN博客 &#x1f495;在校大学生一枚。对IT有着极其浓厚的兴趣 ✨系列专栏目前为C语言初阶、后续会更新c语言的学习方法以及c题目分享. &#x1f60d;希望我的文章对大家有着不一样的帮助&#xff0c;欢迎大家关注我&#xff0c;我也会回关&#xff0c;大家一起交…

LeetCode 热题 100 | 堆(一)

目录 1 什么是堆排序 1.1 什么是堆 1.2 如何构建堆 1.3 举例说明 2 215. 数组中的第 K 个最大元素 2.1 子树大根化 2.2 遍历所有子树 2.3 弹出栈顶元素 2.4 完整代码 菜鸟做题&#xff0c;语言是 C 1 什么是堆排序 1.1 什么是堆 堆的定义和分类&#xff…

为什么Hashtable不允许插入nuIl键和null值?

1、典型回答 浅层次的来回答这个问题的答案是&#xff0c;JDK 源码不支持 Hashtable 插入 value 值为 null&#xff0c;如以下 JDK 源码所示&#xff1a; 也就是 JDK 源码规定了&#xff0c;如果你给 Hashtable 插入 value 值为 null 就会抛出空指针异常。 并且看上面的 JDK …

如何搭建DolphinScheduler服务并结合内网穿透公网远程任务调度

文章目录 前言1. 安装部署DolphinScheduler1.1 启动服务 2. 登录DolphinScheduler界面3. 安装内网穿透工具4. 配置Dolphin Scheduler公网地址5. 固定DolphinScheduler公网地址 前言 本篇教程和大家分享一下DolphinScheduler的安装部署及如何实现公网远程访问&#xff0c;结合内…

Stable Diffusion WebUI 生成参数:宽度/高度/生成批次/每批数量/提示词相关性/随机种子

本文收录于《AI绘画从入门到精通》专栏&#xff0c;专栏总目录&#xff1a;点这里。 大家好&#xff0c;我是水滴~~ 本文将继续了解 Stable Diffusion WebUI 的生成参数&#xff0c;主要内容有&#xff1a;宽度、高度、生成批次、每批数量、提示词相关性、随机种子。希望能对你…

大模型主流微调训练方法总结 LoRA、Adapter、Prefix-tuning、P-tuning、Prompt-tuning 并训练自己的数据集

大模型主流微调训练方法总结 LoRA、Adapter、Prefix-tuning、P-tuning、Prompt-tuning 概述 大模型微调(finetuning)以适应特定任务是一个复杂且计算密集型的过程。本文训练测试主要是基于主流的的微调方法:LoRA、Adapter、Prefix-tuning、P-tuning和Prompt-tuning,并对…

STM32驱动CC1101时的正确配置和一些遇到的坑

项目背景 由于项目需要用到CC1101-Q1,所以买了CC1101的模块和驱动底板,插电脑上一发一收。串口调试助手查看接收端的数据,测试正常。 然后想用STM32来驱动其中的一块CC1101模块作为发送端,和另一个买来的接收端测试通信。找到一份STM32驱动CC1101Demo代码,分为软件模拟S…

Certum代码签名证书

代码签名证书是一种数字证书&#xff0c;用于验证软件开发者身份和保证软件完整性的技术。它的作用在于确保软件在传输和安装过程中没有被篡改或损坏&#xff0c;并且确认软件来自可信的开发者。通过代码签名证书&#xff0c;用户可以更放心地下载和安装软件&#xff0c;降低安…

【Linux】文件描述符 - fd

文章目录 1. open 接口介绍1.1 代码演示1.2 open 函数返回值 2. 文件描述符 fd2.1 0 / 1 / 22.2 文件描述符的分配规则 3. 重定向3.1 dup2 系统调用函数 4. FILE 与 缓冲区 1. open 接口介绍 使用 man open 指令查看手册&#xff1a; #include <sys/types.h> #include …

AI系统性学习06—开源中文语言大模型

1、ChatGLM ChatGLM-6B的github地址&#xff1a;https://github.com/THUDM/ChatGLM-6B ChatGLM-6B 是一个开源的、支持中英双语的对话语言模型&#xff0c;基于 General Language Model (GLM) 架构&#xff0c;具有 62 亿参数。结合模型量化技术&#xff0c;用户可以在消费级…

透视未来工厂:山海鲸可视化打造数字孪生新篇章

在信息化浪潮的推动下&#xff0c;数字孪生工厂项目正成为工业制造领域的新宠。作为一名山海鲸可视化的资深用户&#xff0c;我深感其强大的数据可视化能力和数字孪生技术在工厂管理中的应用价值&#xff0c;同时我们公司之前也和山海鲸可视化合作制作了一个智慧工厂项目&#…

OceanBase生产环境安装部署的最优实践

关于生产环境&#xff0c;为了尽量确保性能和稳定性&#xff0c;我们比较建议采用标准化的配置进行部署&#xff0c;例如接下来会提到的服务初始化、日志管理和数据分盘等关键步骤。而在非生产环境中&#xff0c;如果条件满足&#xff0c;同样建议遵循规范部署的原则。 前期准备…

项目实战-开发工具入门/基本框架搭建/项目初始化/引入组件库

上周更新完了之前vue3的shopping项目&#xff0c;接下来&#xff0c;将会开启一个新的项目&#xff0c;效果是类似于移动端的一个伙伴匹配项目&#xff0c;今天这篇文章从需求分析到架构设计再到项目初始化&#xff0c;基本框架搭建几个部分来为大家详细介绍。 从这个项目开始…

YOLOV5 改进:替换backbone为Swin Transformer

1、前言 本文会将YOLOV5 backbone更换成Swin Transformer 具体为什么这样实现参考上文:YOLOV5 改进:替换backbone(MobileNet为例)-CSDN博客 这里只贴加入的代码 训练结果如下: 2、common文件更改 在common文件中加入下面代码: 这里是swin transformer的实现,参考:…

VMware部署银河麒麟遇到的问题记录

1. 解决VMware Workstation安装VMware Tools显示灰色的办法 1.关闭虚拟机; 2.在虚拟机设置分别设置CD/DVD、CD/DVD2和软盘为自动检测三个步骤; 3.再重启虚拟机,灰色字即点亮。 2.Linux安装vmTool

Pytest接口自动化测试框架搭建模板

auto_api_test 开发环境: Pycharm 开发语言&版本: python3.7.8 测试框架: Pytest、测试报告: Allure 项目源码Git地址 项目目录结构 api – 模仿PO模式, 抽象出页面类, 页面类内包含页面所包含所有接口, 并封装成方法可供其他模块直接调用config – 配置文件目录data…

打造高效自动化渗透测试系统:关键步骤与实践

随着当前网络安全威胁的不断扩展与升级&#xff0c;开展渗透测试工作已经成为广大企业组织主动识别安全漏洞与潜在风险的关键过程。然而&#xff0c;传统的人工渗透测试模式对测试人员的专业能力和经验水平有很高的要求&#xff0c;企业需要投入较大的时间和资源才能完成。在此…

品牌纷纷推出“穷鬼套餐”的原因竟然有这些?

被大众戏称为“千元店”的山姆也开穷鬼套餐了&#xff1f;&#xff1f;该套餐包括1.59公斤售价21.9元的鸡蛋、7个装售价23.9元的原味贝果以及16片装售价59.8元的瑞士卷。 穷鬼套餐最开始是麦当劳和肯德基的推出的概念&#xff0c;由麦当劳推出的11随心配、肯德基超值午餐等。为…