【Linux】线程的互斥

作者:羚羊角uou日期:2025/10/6

因为线程是共享地址空间的,就会共享大部分资源,这种共享资源就是公共资源,当多执行流访问公共资源的时候,就会出现各种情况的数据不一致问题。为了解决这种问题,我们就需要学习线程的同步与互斥,本篇将介绍线程的互斥。

1.相关概念

  • 临界资源:多线程执⾏流被保护的共享资源就叫做临界资源
  • 临界区:每个线程内部,访问临界资源的代码,就叫做临界区
  • 互斥:任何时刻,互斥保证有且只有⼀个执⾏流进⼊临界区,访问临界资源,通常对临界资源起保护作⽤
  • 原⼦性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成

1.1 数据不一致

⼤部分情况,线程使⽤的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量

归属单个线程,其他线程⽆法获得这种变量。 但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。

下面是一个模拟抢票的简单代码。

1#include <stdio.h>
2#include <stdlib.h>
3#include <string.h>
4#include <unistd.h>
5#include <pthread.h>
6
7int ticket = 100;
8void *route(void *arg)
9{
10    char *id = (char *)arg;
11    while (1)
12    {
13        if (ticket > 0) // 判断票的数量
14        {
15            usleep(1000);                               // 模拟抢票的时候花费的时间
16            printf("%s sells ticket:%d\n", id, ticket); // 假设这里就是抢到票了
17            ticket--;                                   // 更新票的数量
18        }
19        else
20        {
21            break;
22        }
23    }
24    return nullptr;
25}
26
27int main()
28{
29    pthread_t t1, t2, t3, t4;
30    pthread_create(&t1, NULL, route, (void *)"thread 1");
31    pthread_create(&t2, NULL, route, (void *)"thread 2");
32    pthread_create(&t3, NULL, route, (void *)"thread 3");
33    pthread_create(&t4, NULL, route, (void *)"thread 4");
34
35    pthread_join(t1, NULL);
36    pthread_join(t2, NULL);
37    pthread_join(t3, NULL);
38    pthread_join(t4, NULL);
39    return 0;
40}

运行后会发现,这个票数居然还减到了负数。

主要是因为usleep让所有线程在判断tickets>0时,全部进到判断里,但是usleep却不让线程往后执行,大大提升了线程被同时进到临界区的机会,tickets就会被减到负数。(不一致原因详情:课42)

全局资源没有加保护就可能会有并发问题,这也是线程安全问题。

1.2 见一见锁

解决上面出现的问题我们可以给临界区代码加锁。

mutex互斥锁,也叫互斥量,它的类型就叫pthread_mutex_t,使用锁需要头文件pthread.h

使用的时候先对锁初始化,直接用PTHREAD_MUTEX_INITIALIZER这个宏初始化就行。

1int ticket = 100;
2pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; // 定义一把锁并初始化
3void *route(void *arg)
4{
5    char *id = (char *)arg;
6    while (1)
7    {
8        pthread_mutex_lock(&lock); // 加锁
9        if (ticket > 0) // 判断票的数量
10        {
11            usleep(1000);                               // 模拟抢票的时候花费的时间
12            printf("%s sells ticket:%d\n", id, ticket); // 假设这里就是抢到票了
13            ticket--;                                   // 更新票的数量
14            pthread_mutex_unlock(&lock); // 解锁
15        }
16        else
17        {
18            pthread_mutex_unlock(&lock); // 防止走到else时锁没解
19            break;
20        }
21    }
22    return nullptr;
23}

可以看到加锁之后就没有出现数据被减到负数了,而且还能感受到这个代码的运行速度变慢了。

2.认识mutex

  • 全局的锁:这种方式定义的锁不用被释放,程序运行结束会自动释放。
  • 局部的锁:就要用到相关的函数,初始化锁的函数第二个参数就是锁的一些属性,不用管。局部的锁要调用destroy释放。
  • 不管是全局的还是局部的锁,线程在访问公共资源之前都要申请锁,lock加锁,unlock解锁。线程申请锁成功,继续向后运行,申请失败会阻塞挂起申请执行流。trylock是非阻塞版本,不考虑。

所有线程都要竞争申请锁,所以首先所有线程都要看到锁,所以锁本身就是临界资源;锁是用来保护临界区资源的,但是谁来保护锁?所以要求锁的申请和解除必须是原子的

锁提供的能力本质就是:执行临界区代码的执行流由并行转为串行

2.1 接口使用

前面我们已经使用过全局的锁了,现在就用一下局部锁。

1#include <stdio.h>
2#include <stdlib.h>
3#include <string.h>
4#include <unistd.h>
5#include <pthread.h>
6#include <string>
7#include <iostream>
8
9int ticket = 100;
10struct Data
11{
12    Data(const std::string &name, pthread_mutex_t *plock)
13        : _name(name),
14          _plock(plock)
15    {}
16
17    std::string _name;
18    pthread_mutex_t *_plock;
19};
20
21void *route(void *arg)
22{
23    Data* d = static_cast<Data *>(arg);
24    while (1)
25    {
26        pthread_mutex_lock(d->_plock); // 加锁
27        if (ticket > 0)
28        {
29            usleep(1000);
30            printf("%s sells ticket:%d\n", d->_name.c_str(), ticket);
31            ticket--;
32            pthread_mutex_unlock(d->_plock); // 解锁
33        }
34        else
35        {
36            pthread_mutex_unlock(d->_plock); // 防止走到else时锁没解
37            break;
38        }
39    }
40    return nullptr;
41}
42
43int main()
44{
45    pthread_mutex_t lock;  // 局部锁
46    pthread_mutex_init(&lock, nullptr); // 对锁初始化
47
48    Data d1("thread 1", &lock);
49    Data d2("thread 2", &lock);
50    Data d3("thread 3", &lock);
51    Data d4("thread 4", &lock);
52
53    pthread_t t1, t2, t3, t4;
54    pthread_create(&t1, NULL, route, &d1);
55    pthread_create(&t2, NULL, route, &d2);
56    pthread_create(&t3, NULL, route, &d3);
57    pthread_create(&t4, NULL, route, &d4);
58
59    pthread_join(t1, NULL);
60    pthread_join(t2, NULL);
61    pthread_join(t3, NULL);
62    pthread_join(t4, NULL);
63
64    pthread_mutex_destroy(&lock); // 销毁锁
65
66    return 0;
67}

操作还是比较简单的。

对临界区资源进行保护,本质就是用锁对临界区代码进行保护。

  • 加锁之后,在临界区内部,依旧允许线程切换,因为当前线程并没有释放锁,依旧持有锁,带着锁被切换的,其他的线程必须等我回来执行完代码,将锁释放后,他们才可以展开对锁的竞争从而进入临界区。
  • 这把锁要么没被使用要么已经被使用完了,这两种状态才对其他线程有意义,这就体现了原子性。
  • 在线程访问临界区资源时不会被其他线程打扰,也是一种变相的原子性的表现。

2.2 mutex的原理

硬件实现:关闭时钟中断(了解即可)。

软件实现:为了实现互斥锁操作,⼤多数体系结构都提供了swapexchange汇编指令,该指令的作⽤是把寄存器和内存单元的数据相交换,由于只有⼀条指令,保证了原⼦性,下面有段伪代码。

申请锁

进程/线程切换:CPU内部的寄存器硬件只有一套,但是CPU寄存器的数据可以有多份,每份就是当前执行流的上下文数据。

把一个变量的内容换到CPU内部,其实就是把变量的内容获取到当前执行流的硬件上下文中,CPU寄存器的硬件上下文属于进程/线程私有的。

我们用swapexchange将内存中的变量交换到寄存器中,其实就是当前进程/线程在获取锁,是交换,而不是拷贝,所以锁只有一份,谁申请谁持有。

当后面来的执行流想申请锁,首先会把寄存器清0,然后在交换的这一步时,就只会用0换0,因为这个1已经被之前的线程申请走了,此时申请锁失败,线程就会阻塞挂起。

解锁

解锁的时候,只需要往内存里的mutex写1

2.3 C++里的mutex

1#include <mutex> //需要包含的头文件
2
3std::mutex cpp_mutex;  //定义锁
4
5cpp_mutex.lock(); //加锁
6cpp_mutex.unlock(); //解锁
7

3.封装mutex

1//Mutex.hpp文件
2#include <iostream>
3#include <pthread.h>
4#include <cstring>
5#include <cstdio>
6
7namespace MyMutex
8{
9    class Mutex
10    {
11    public:
12        Mutex()
13        {
14            pthread_mutex_init(_plock, nullptr); // 锁初始化
15        }
16        void Lock() // 加锁
17        {
18            int n = pthread_mutex_lock(_plock);
19            if (n != 0)
20                std::cerr << "pthread_mutex_lock fail: " << strerror(n) << std::endl;
21        }
22        void UnLock() // 解锁
23        {
24            int n = pthread_mutex_unlock(_plock);
25            if (n != 0)
26                std::cerr << "pthread_mutex_unlock fail: " << strerror(n) << std::endl;
27        }
28
29        ~Mutex()
30        {
31            pthread_mutex_destroy(_plock); // 锁释放
32        }
33
34    private:
35        pthread_mutex_t *_plock;
36    };
37}
1//测试
2#include <stdio.h>
3#include <stdlib.h>
4#include <string.h>
5#include <unistd.h>
6#include <string>
7#include <iostream>
8#include "Mutex.hpp"
9
10using namespace MyMutex;
11
12int ticket = 100;
13struct Data
14{
15    Data(const std::string &name, Mutex *plock)
16        : _name(name),
17          _plock(plock)
18    {
19    }
20
21    std::string _name;
22    Mutex *_plock;
23};
24
25void *route(void *arg)
26{
27    Data *d = static_cast<Data *>(arg);
28    while (1)
29    {
30        d->_plock->Lock(); // 加锁
31        if (ticket > 0)
32        {
33            usleep(1000);
34            printf("%s sells ticket:%d\n", d->_name.c_str(), ticket);
35            ticket--;
36            d->_plock->UnLock(); // 解锁
37        }
38        else
39        {
40            d->_plock->UnLock(); // 解锁
41            break;
42        }
43    }
44    return nullptr;
45}
46
47int main()
48{
49    Mutex lock; //用自己实现的锁
50    Data d1("thread 1", &lock);
51    Data d2("thread 2", &lock);
52    Data d3("thread 3", &lock);
53    Data d4("thread 4", &lock);
54
55    pthread_t t1, t2, t3, t4;
56    pthread_create(&t1, NULL, route, &d1);
57    pthread_create(&t2, NULL, route, &d2);
58    pthread_create(&t3, NULL, route, &d3);
59    pthread_create(&t4, NULL, route, &d4);
60
61    pthread_join(t1, NULL);
62    pthread_join(t2, NULL);
63    pthread_join(t3, NULL);
64    pthread_join(t4, NULL);
65
66    return 0;
67}

我们还可以进一步封装这个锁,让他可以自动的加锁解锁。需要在实现一个LockGuard类。

1#include <iostream>
2#include <pthread.h>
3#include <cstring>
4#include <cstdio>
5
6namespace MyMutex
7{
8    class Mutex
9    {
10    public:
11        Mutex()
12        {
13            pthread_mutex_init(_plock, nullptr); // 锁初始化
14        }
15        void Lock() // 加锁
16        {
17            int n = pthread_mutex_lock(_plock);
18            if (n != 0)
19                std::cerr << "pthread_mutex_lock fail: " << strerror(n) << std::endl;
20        }
21        void UnLock() // 解锁
22        {
23            int n = pthread_mutex_unlock(_plock);
24            if (n != 0)
25                std::cerr << "pthread_mutex_unlock fail: " << strerror(n) << std::endl;
26        }
27
28        ~Mutex()
29        {
30            pthread_mutex_destroy(_plock); // 锁释放
31        }
32
33    private:
34        pthread_mutex_t *_plock;
35    };
36
37    class LockGuard
38    {
39    public:
40        LockGuard(Mutex *mutex)
41            : _mutex(mutex)
42        {
43            _mutex->Lock(); // 构造时加锁
44        }
45
46        ~LockGuard()
47        {
48            _mutex->UnLock(); // 析构时解锁
49        }
50
51    private:
52        Mutex *_mutex;
53    };
54}
1void *route(void *arg)
2{
3    Data *d = static_cast<Data *>(arg);
4    while (1)
5    {
6        {
7            LockGuard lock_guard(d->_plock);
8            if (ticket > 0)
9            {
10                usleep(1000);
11                printf("%s sells ticket:%d\n", d->_name.c_str(), ticket);
12                ticket--;
13            }
14            else
15            {
16                break;
17            }
18        }
19    }
20    return nullptr;
21}

这个就叫做RAII风格的互斥锁实现。

本篇分享就到这里,我们下篇见~


【Linux】线程的互斥》 是转载文章,点击查看原文


相关推荐


MySQL Performance Schema详解与实战应用
IT橘子皮2025/10/5

Performance Schema是MySQL内置的性能监控系统,自5.5版本引入以来已成为数据库性能分析与优化的核心工具。本文将全面解析其架构原理、配置方法及典型应用场景,帮助您掌握这一强大的性能诊断利器。 一、Performance Schema核心架构 Performance Schema采用插桩-消费者模型构建,通过轻量级的内存表存储性能数据,对数据库性能影响通常控制在5%以内。其核心组件包括: ​插桩点(Instruments)​​:嵌入MySQL代码的探测点,按层级命名如wai


【Unity笔记】Unity XR 模式下 Point Light 不生效的原因与解决方法
EQ-雪梨蛋花汤2025/10/4

Unity XR 模式下 Point Light 不生效的原因与解决方法 在 Unity 中开发 VR 应用时,经常会遇到一个让人疑惑的现象: 在 编辑器 Game 模式下,场景中的 Point Light(点光源) 可以正常照亮物体。但当启用 Initialize XR on Startup 并通过 VR 设备运行时,Point Light 不再生效,只有 Directional Light(平行光) 仍然有效。 这让很多开发者误以为“材质只支持 Directional Light,而不支持


XYplorer(多标签文件管理器) 多语便携版
东风西巷2025/10/2

XYplorer中文版是一款多标签文件管理器及增强资源管理器的工具,XYplorer文件管理器支持多标签页栏,管理文件时跟使用Chrome之类浏览器一样,从浏览方便性,和切换滑顺程度,要比Windows系统自带的Explorer资源管理器便捷得多.可以大部分程度上替代系统自带的文件管理器.同时,有浏览器快捷键和鼠标快捷. 软件功能 双窗口浏览:支持双窗口浏览,可以同时浏览两个文件夹,方便文件的复制、移动和比较。 高级搜索:支持高级搜索功能,可以根据文件名、大小、日期、属性等多种条件进


什么是 Apache Ignite?
悟能不能悟2025/10/2

首先需要明确一点:“Ignite”这个名字在技术领域可能指代不同的事物,但最著名和广泛使用的是 ​Apache Ignite。它是一个功能强大的、分布式内存计算平台。除此之外,还有例如 ​Couchbase Ignite​(一个会议)等。本文将重点介绍 ​Apache Ignite。 什么是 Apache Ignite? Apache Ignite 是一个以内存为中心的分布式数据库、缓存和处理平台,设计用于在横向扩展的架构上提供极高的性能和吞吐量。你可以把它理解为一个“内存数据网格”,但其


Python零基础入门:30分钟掌握核心语法与实战应用
做运维的阿瑞2025/10/2

Python基础入门指南 5分钟掌握核心概念,15分钟上手实战项目 你将学到什么 核心技能实际应用学习时间🔢 数据类型处理文本、数字、列表10分钟🔄 控制流程循环、判断、函数15分钟📊 数据处理文件操作、数据分析20分钟🎮 实战项目猜数字游戏30分钟 适合人群 零基础新手 | 转语言开发者 | 在校学生 | 职场提升 快速开始 三个核心场景 数据处理 # 处理学生成绩 scores = [85, 92, 78, 96, 88, 76, 94, 82] #


软件工程实践团队作业——团队组建与实践选题
Funny Valentine-js10/1/2025

吴彦组。


Qt Widgets 应用程序核心类 - QApplication 详解
会飞的胖达喵9/30/2025

摘要:QApplication是Qt Widgets应用的核心类,负责GUI应用程序的控制流和全局设置。它继承自QGuiApplication和QCoreApplication,提供样式管理、调色板/字体设置、用户交互参数配置以及窗口管理等功能。通过qApp宏可全局访问应用实例,支持运行时动态调整界面风格。示例展示了QApplication的初始化、属性设置、样式更改和事件循环管理,以及高级功能如字体大小控制和样式表应用,体现了其在Qt Widgets开发中的核心作用。


大数据毕业设计选题推荐-基于大数据的全球产品库存数据分析与可视化系统-大数据-Spark-Hadoop-Bigdata
IT研究室2025/10/8

✨作者主页:IT研究室✨ 个人简介:曾从事计算机专业培训教学,擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python项目 安卓项目 微信小程序项目 文章目录 一、前言二、开发环境三、系统界面展示四、代码参考五、系统视频结语 一、前言 系统介绍 本系统是一个基于大数据技术的全球产品库存数据分析与可视化系统,采用Hado


深入浅出 Compose 测量机制
Pika2025/10/9

自从换了新工作后,好久没有写博客了,今天终于能有时间写点东西,Compose作为Android新一代UI框架,已经得到了很多公司的认可,未来市场对Compose的要求也逐步提高。如果大家对Compose有兴趣,也欢迎后台私信我,字节移动OS招聘Compose框架的二次定制开发的Android小伙伴,一起把Compose做大做强吧! UI框架的测量流程 对于UI框架来说,测量布局与绘制可谓是非常重要的三个话题,对于Compose来说也不例外,本章我们将从着Compose的原理出发,来聊一下最重要


C/C++黑客帝国代码雨
Want5952025/10/10

写在前面 数字雨,又被称为“黑客帝国雨”,是一种经典的视觉效果,常用于表现科幻、科技感十足的场景。这种效果最初在电影《黑客帝国》中出现,以绿色字符从屏幕顶端不断下落的方式,营造出一种神秘而充满未来感的氛围。本文将介绍如何使用C语言在Windows控制台中实现一个简易的数字雨效果。通过这篇文章,你不仅能了解如何利用控制台API进行绘图操作,还能体会到字符动画背后的技术逻辑与美感。 系列文章 序号直达链接1C/C++李峋同款跳动的爱心2C/C++跳动的爱心3C/C++经典爱心4C/C++满

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0