0

0

Linux线程互斥锁

雪夜

雪夜

发布时间:2025-07-23 08:02:02

|

646人浏览过

|

来源于php中文网

原创

引言

大家有任何疑问,可以在评论区留言或者私信我,我一定尽力解答。

今天我们学习Linux线程互斥的话题。Linux同步和互斥是Linux线程学习的延伸。但这部分挺有难度的,请大家做好准备。那我们就正式开始了。

?看现象,说原因

我们先上一段代码:

代码语言:javascript代码运行次数:0运行复制
<code class="javascript">#include<iostream>#include<pthread.h>#include<unistd.h>#include<vector>#include<cassert>using namespace std;int NUM=5;int ticket=1000;class pthread{public:     char buffer[1024];     pthread_t id;};void *get_ticket(void *args){     pthread *pth=static_cast<pthread*>(args);     while(1)     {          usleep(1234);          if(ticket<0)          {               return nullptr;          }          cout<<pth->buffer<<" is ruuing ticket: "<<ticket<<endl;          ticket--;               }}int main(){     vector<pthread*> pthpool;     for(int i=0;i<NUM;i++)     {          pthread* new_pth=new pthread();          snprintf(new_pth->buffer,sizeof (new_pth->buffer),"thread-%d",i+1);          int n=pthread_create(&(new_pth->id),nullptr,get_ticket,new_pth);          assert(n==0);          (void)n;          pthpool.push_back(new_pth);     }     for(int i=0;i<pthpool.size();i++)     {         int m= pthread_join(pthpool[i]->id,nullptr);         assert(m==0);         (void)m;     }     return 0;}</code>

这段代码模拟的是抢票模型,一共有一千张票,我们让几个线程同时去抢票。看看有什么不符合实际的情况发生。

Linux线程互斥锁

还真有不符合实际的情况发生:竟然抢到了负票。卧槽,这是什么情况,我们赶紧分析一下。

首先,在代码中我们定义了一个全局变量:ticket 。这个变量被所有线程所共享。

对于这种情形,我们直接拉向极端情况:假设此时的票数只有一张了。一个线程进入if内部,但是对票数还没有进行操作,这时,时间片到了,这个线程被切了下去。紧接着,一个线程就通过if判断,顺利抢到了最后一张票,对票数进行了操作。此时已经无票可抢了。这时,那个被切下来的线程又带着它的数据开始了抢票。但是在这个线程看来,票数依旧还有最后一张,所以,它又对票数进行了减减操作,得到了负票。

这种情况显然是不合理的,假如一个电影院有100个座位,结果卖出去102张票,这怎么可以呢?

这就是个坑啊,必须解决。

?解决方案

在提出解决方案之前,我们先回顾几个概念。

多个执行流进行安全访问的共享资源,叫做临界资源我们把多个执行流中,访问临界资源的代码叫做临界区,临界区往往是线程代码很小的一部分。想让多个线程串行访问共享资源的方式叫做互斥。对一个资源进行访问的时候,要么不做,要么做完,这种特性叫做原子性。一个对资源进行的操作,如果只有一挑汇编语句完成,那么就是原子的,反之就不是原则的。这是当前我们对原子性的理解,后面还会发生改变。

我们提出的解决方案就是加锁。相信大家第一次听到锁。对于什么是锁,如何加锁,锁的原理是什么我们都不清楚,别着急,我们在接下来的内容里会进行详细的详解。

我们先使用一下锁,见见猪跑!!

代码语言:javascript代码运行次数:0运行复制
<code class="javascript">#include<iostream>#include<pthread.h>#include<unistd.h>#include<vector>#include<cassert>using namespace std;int NUM=5;int ticket=1000;pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;class pthread{public:     char buffer[1024];     pthread_t id;};void *get_ticket(void *args){     pthread *pth=static_cast<pthread*>(args);     while(1)     {            pthread_mutex_lock(&mutex);          usleep(1234);                  if(ticket<0)          {                 pthread_mutex_unlock(&mutex);               return nullptr;                       }          cout<<pth->buffer<<" is ruuing ticket: "<<ticket<<endl;          ticket--;          pthread_mutex_unlock(&mutex);               }}int main(){     vector<pthread*> pthpool;     for(int i=0;i<NUM;i++)     {          pthread* new_pth=new pthread();          snprintf(new_pth->buffer,sizeof (new_pth->buffer),"user-%d",i+1);          int n=pthread_create(&(new_pth->id),nullptr,get_ticket,new_pth);          assert(n==0);          (void)n;          pthpool.push_back(new_pth);     }     for(int i=0;i<pthpool.size();i++)     {         int m= pthread_join(pthpool[i]->id,nullptr);         assert(m==0);         (void)m;     }     return 0;}</code>
Linux线程互斥锁

结果显示抢票的过程非常顺利,接下来,我们把重心指向锁。

?互斥锁

首先,我们先认识一些锁的常见接口

代码语言:javascript代码运行次数:0运行复制
<code class="javascript">// 所有锁的相关操作函数都在这个头文件下//这些函数如果又返回值,操作成功的话,返回0,失败的话。返回错误码。错误原因被设置#include <pthread.h>// 锁的类型,用来创建锁pthread_mutex_t// 对锁进行初始化,第二个参数一般设位nullint pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);// 如果这个锁没有用了,可以调用该函数对锁进行销毁int pthread_mutex_destroy(pthread_mutex_t *mutex);// 如果创建的锁是全局变量,可以这样初始化。pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;// 对特定代码部分进行上锁,这部分代码只能有一次只能有一个执行流进入,被保护的资源叫做临界资源。int pthread_mutex_lock(pthread_mutex_t *mutex);// 尝试上锁,不一定成功。int pthread_mutex_trylock(pthread_mutex_t *mutex);// 取消锁。int pthread_mutex_unlock(pthread_mutex_t *mutex);</code>

刚刚,我们已经使用一种方式实现了加锁,接下来,我们用另一种方式:

代码语言:javascript代码运行次数:0运行复制
<code class="javascript">#include <iostream>#include <pthread.h>#include <unistd.h>#include <vector>#include <cassert>using namespace std;int NUM = 5;int ticket = 1000;class Thread_Data{public:     Thread_Data(string name,pthread_mutex_t* mutex):_name(name),_mutex(mutex)     {}     ~Thread_Data()     {}public: string _name; pthread_mutex_t*  _mutex;};void *get_ticket(void *args){     Thread_Data *pth = static_cast<Thread_Data*>(args);     while (1)     {          pthread_mutex_lock(pth->_mutex);           if (ticket > 0)          {                 usleep(1234);                cout << pth->_name << " is ruuing ticket: " << ticket << endl;                 ticket--;               pthread_mutex_unlock(pth->_mutex);           }           else          {               pthread_mutex_unlock(pth->_mutex);               break;          }     }}int main(){     pthread_mutex_t mutex;     pthread_mutex_init(&mutex,nullptr);     vector<pthread_t> tids(NUM);      for (int i = 0; i < NUM; i++)     {          char buffer[1024];          Thread_Data *td=new Thread_Data(buffer,&mutex);          snprintf(buffer, sizeof(buffer), "user-%d", i + 1);                      int n =pthread_create(&tids[i], nullptr, get_ticket, td);          assert(n == 0);          (void)n;     }     for (int i = 0; i < tids.size(); i++)     {          int m = pthread_join(tids[i], nullptr);          assert(m == 0);          (void)m;     }     return 0;}</code>
Linux线程互斥锁

运行一下,发现一直是4号线程在跑,其他线程呢?我也没让其他线程退出呀!而且抢票的时间变长了。

加锁和解锁是多个线程串行进行的,所以程序允许起来会变得很慢。锁只规定互斥访问,没有规定谁优先访问。锁就是让多个线程公平竞争的结果,强者胜出嘛。 ?关于互斥锁的理解所有的执行流都可以访问这一把锁,所以锁是一个共享资源。加锁和解锁的过程必须是原子的,不会存在中间状态。要么成功,要么失败。加锁的过程必须是安全的。谁持有锁,谁进入临界区。

这种情况试一试不就知道了。我们依旧使用上面的一份代码,稍稍做一下修改:

Linux线程互斥锁
Linux线程互斥锁

所以,当一个执行流申请锁失败时,这个执行流会阻塞在这里。


?关于原子性的理解

如图,三个执行流

Runwayml(AI painting)
Runwayml(AI painting)

Runway 平台的文本生成图像AI工具

下载
Linux线程互斥锁

问:如果线程1申请锁成功,进入临界资源,正在访问临界资源区的时候,其他线程在做什么?

答:都在阻塞等待,直到持有锁的线程释放锁。

问; 如果线程1申请锁成功,进入临界资源,正在访问临界资源区的时候,可不可以被切换?

答:绝对是可以的,CPU管你有没有锁呢,时间片到了你必须下来。当持有锁的线程被切下来的时候,

是抱着锁走的,即使自己被切走了,其他线程依旧无法申请锁成功,也就无法继续向后执行。

这就叫作:江湖上没有我,但依旧有我的传说。

所以,未来我们在使用锁的时候,要遵守什么样的原则呢?

一定要保证代码的粒度(锁要保护的代码的多少i)要非常小。加锁是程序员的行为,必须做到要加的话所有的线程必须要加锁。?如何理解加锁和解锁是原子的

在分析如何实现加锁和解锁之前,我们先形成几个共识:

CPU内执行流只有一套,且被所有执行流所共享。CPU内寄存器的内容属线程所有,是每个执行流的上下文。时间片到达,数据带走。在进行加锁和解锁的时候,这个线程随时会因时间片已到而被换下来。

为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性 。

如图:

Linux线程互斥锁

我们假设有线程A,B两个线程,A想要获得锁

锁内存储的数据就是int类型的1。 A线程中有数字0。

交换的过程由一条汇编构成

交换的本质:共享的数据,交换到线程的上下文中。

那么。如何完成解锁的操作呢。解锁的操作特别简单,只需一步。

?对互斥锁的简单封装

相信大家对互斥锁都有了充分的了解。接下来,我们就实现一下对互斥锁的简单封装。

代码语言:javascript代码运行次数:0运行复制
<code class="javascript">#include <iostream>#include <pthread.h>#include <unistd.h>#include <vector>#include <cassert>class Mutex{public:     Mutex(pthread_mutex_t *mutex) : _mutex(mutex)     {     }     void unlock()     {          if (_mutex)          {                pthread_mutex_unlock(_mutex);          }     }     void lock()     {         if(_mutex)         {               pthread_mutex_lock(_mutex);         }     }     ~Mutex()     {     }public:     pthread_mutex_t *_mutex;};class Lockguard{public:     Lockguard(Mutex mutex) : _mutex(mutex)     {          _mutex.lock();     }     ~Lockguard()     {          _mutex.unlock();     }public:     Mutex _mutex;};</code>

这种利用变量出了函数作用域自动销毁的性质,我们称之为RAII特性。

到这里,我们本篇的内容也就结束了,我们期待下一期博客相遇。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
if什么意思
if什么意思

if的意思是“如果”的条件。它是一个用于引导条件语句的关键词,用于根据特定条件的真假情况来执行不同的代码块。本专题提供if什么意思的相关文章,供大家免费阅读。

846

2023.08.22

全局变量怎么定义
全局变量怎么定义

本专题整合了全局变量相关内容,阅读专题下面的文章了解更多详细内容。

89

2025.09.18

python 全局变量
python 全局变量

本专题整合了python中全局变量定义相关教程,阅读专题下面的文章了解更多详细内容。

106

2025.09.18

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

970

2023.08.02

int占多少字节
int占多少字节

int占4个字节,意味着一个int变量可以存储范围在-2,147,483,648到2,147,483,647之间的整数值,在某些情况下也可能是2个字节或8个字节,int是一种常用的数据类型,用于表示整数,需要根据具体情况选择合适的数据类型,以确保程序的正确性和性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

605

2024.08.29

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

294

2025.08.29

C++中int的含义
C++中int的含义

本专题整合了C++中int相关内容,阅读专题下面的文章了解更多详细内容。

212

2025.08.29

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

1846

2023.10.19

JavaScript浏览器渲染机制与前端性能优化实践
JavaScript浏览器渲染机制与前端性能优化实践

本专题围绕 JavaScript 在浏览器中的执行与渲染机制展开,系统讲解 DOM 构建、CSSOM 解析、重排与重绘原理,以及关键渲染路径优化方法。内容涵盖事件循环机制、异步任务调度、资源加载优化、代码拆分与懒加载等性能优化策略。通过真实前端项目案例,帮助开发者理解浏览器底层工作原理,并掌握提升网页加载速度与交互体验的实用技巧。

23

2026.03.06

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PostgreSQL 教程
PostgreSQL 教程

共48课时 | 10.3万人学习

Git 教程
Git 教程

共21课时 | 4万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号