Linux线程互斥锁

引言

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

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

?看现象,说原因

我们先上一段代码:

代码语言:javascript代码运行次数:0运行复制

#include#include#include#include#includeusing 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(args);     while(1)     {          usleep(1234);          if(ticket<0)          {               return nullptr;          }          cout<buffer<<" is ruuing ticket: "<<ticket<<endl;          ticket--;               }}int main(){     vector pthpool;     for(int i=0;ibuffer,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;iid,nullptr);         assert(m==0);         (void)m;     }     return 0;}

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

Linux线程互斥锁

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

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

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

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

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

?解决方案

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

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

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

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

代码语言:javascript代码运行次数:0运行复制

#include#include#include#include#includeusing 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(args);     while(1)     {            pthread_mutex_lock(&mutex);          usleep(1234);                  if(ticket<0)          {                 pthread_mutex_unlock(&mutex);               return nullptr;                       }          cout<buffer<<" is ruuing ticket: "<<ticket<<endl;          ticket--;          pthread_mutex_unlock(&mutex);               }}int main(){     vector pthpool;     for(int i=0;ibuffer,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;iid,nullptr);         assert(m==0);         (void)m;     }     return 0;}
Linux线程互斥锁

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

?互斥锁

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

代码语言:javascript代码运行次数:0运行复制

// 所有锁的相关操作函数都在这个头文件下//这些函数如果又返回值,操作成功的话,返回0,失败的话。返回错误码。错误原因被设置#include // 锁的类型,用来创建锁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);

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

代码语言:javascript代码运行次数:0运行复制

#include #include #include #include #include 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(args);     while (1)     {          pthread_mutex_lock(pth->_mutex);           if (ticket > 0)          {                 usleep(1234);                cout <_name << " is ruuing ticket: " << ticket <_mutex);           }           else          {               pthread_mutex_unlock(pth->_mutex);               break;          }     }}int main(){     pthread_mutex_t mutex;     pthread_mutex_init(&mutex,nullptr);     vector 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;}
Linux线程互斥锁

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

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

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

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

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

?关于原子性的理解

如图,三个执行流

豆包AI编程 豆包AI编程

豆包推出的AI编程助手

豆包AI编程 483 查看详情 豆包AI编程

Linux线程互斥锁

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

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

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

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

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

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

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

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

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

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

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

如图:

Linux线程互斥锁

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

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

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

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

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

?对互斥锁的简单封装

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

代码语言:javascript代码运行次数:0运行复制

#include #include #include #include #include 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;};

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

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

以上就是Linux线程互斥锁的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/453064.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
有哪些必装的 VSCode 扩展可以提升前端开发体验?
上一篇 2025年11月8日 00:13:22
mac笔记本怎么装windows系统
下一篇 2025年11月8日 00:13:23

相关推荐

  • Python命令怎样使用profile分析脚本性能 Python命令性能分析的基础教程

    使用Python的cProfile模块分析脚本性能最直接的方式是通过命令行执行python -m cProfile your_script.py,它会输出每个函数的调用次数、总耗时、累积耗时等关键指标,帮助定位性能瓶颈;为进一步分析,可将结果保存为文件python -m cProfile -o ou…

    2026年5月10日
    000
  • c++如何实现UDP通信_c++基于UDP的网络通信示例

    UDP通信基于套接字实现,适用于实时性要求高的场景。1. 流程包括创建套接字、绑定地址(接收方)、发送(sendto)与接收(recvfrom)数据、关闭套接字;2. 服务端监听指定端口,接收客户端消息并回传;3. 客户端发送消息至服务端并接收响应;4. 跨平台需处理Winsock初始化与库链接,编…

    2026年5月10日
    100
  • 谷歌浏览器如何截图 谷歌浏览器页面截图技巧

    谷歌浏览器如何截图 谷歌浏览器页面截图技巧谷歌浏览器如何截图 谷歌浏览器页面截图技巧谷歌浏览器如何截图 谷歌浏览器页面截图技巧谷歌浏览器如何截图 谷歌浏览器页面截图技巧

    使用谷歌浏览器的开发者工具截图步骤:1. 按ctrl+shift+i(windows/linux)或cmd+option+i(mac)打开开发者工具。2. 点击右上角三个点,选择”更多工具”,再选择”截图”。3. 选择截取整个页面。推荐的谷歌浏览器扩展…

    2026年5月10日 用户投稿
    100
  • pycharm解析器怎么添加 解析器添加详细流程

    在pycharm中添加解析器的步骤包括:1) 打开pycharm并进入设置,2) 选择project interpreter,3) 点击齿轮图标并选择add,4) 选择解析器类型并配置路径,5) 点击ok完成添加。添加解析器后,选择合适的类型和版本,配置环境变量,并利用解析器的功能提高开发效率。 在…

    2026年5月10日
    000
  • JavaScript Electron桌面应用

    答案:使用JavaScript开发%ignore_a_1%桌面应用需结合Web技术与Node.js,通过主进程管理窗口、渲染进程展示界面,并利用IPC通信,调用系统功能如文件对话框,最后用electron-builder打包发布,注意安全与进程职责分离。 用JavaScript开发Electron桌…

    2026年5月10日
    000
  • Linux文件系统iostat命令使用技巧

    Linux文件系统iostat命令使用技巧Linux文件系统iostat命令使用技巧Linux文件系统iostat命令使用技巧Linux文件系统iostat命令使用技巧

    iostat是Linux系统中用于监控I/O设备负载的关键工具,能分析磁盘性能并识别瓶颈。默认输出包括CPU使用率和设备I/O统计,分为系统启动以来的平均值和当前采样周期数据。核心指标有:%util反映设备利用率,持续接近100%可能表示I/O瓶颈;await为平均I/O等待时间,过高说明响应变慢;…

    2026年5月10日 用户投稿
    000
  • 如何测试html5编码_测试HTML5页面编码兼容性方法【编码测试】

    HTML5页面编码兼容性测试需五步:一查meta charset是否正确且前置;二验HTTP响应头Content-Type charset是否为utf-8;三用file或chardet工具探测实际编码;四跨浏览器测试URL参数中中文、Emoji解析;五通过W3C验证服务检查编码声明与字节一致性。 如…

    2026年5月10日
    100
  • 后缀php怎么打开_php文件打开方式与运行环境搭建指南

    要打开PHP文件需根据用途选择方式:查看代码可用文本编辑器或IDE,运行则需服务器环境。推荐新手使用XAMPP、WAMP等集成环境,将文件放入htdocs目录后访问localhost;开发者可利用PHP内置服务器,命令行执行php -S localhost:8000运行;高级用户可手动配置Apach…

    2026年5月10日
    000
  • Go语言:检查预编译库的构建版本与平台信息

    本文详细介绍了如何利用go语言内置的`go tool pack`工具,从预编译的go静态库(`.a`文件)中提取其构建信息,包括go编译器版本、操作系统和cpu架构。当`go build`因库版本不匹配而失败时,此方法能帮助开发者准确诊断问题,确保构建环境与库的兼容性。 在Go语言的开发实践中,我们…

    2026年5月10日
    000
  • 解决Python脚本中相对路径文件找不到的常见问题与策略

    本文旨在解决python脚本中因相对路径处理不当导致的文件找不到错误,尤其是在项目迁移后。文章将深入探讨python中相对路径的工作原理、当前工作目录(cwd)的影响,并提供使用`os.getcwd()`诊断问题以及利用`os.path.dirname(__file__)`结合`os.path.jo…

    2026年5月10日
    000
  • Golang如何提升TCP长连接处理效率_Golang TCP长连接处理性能优化实践详解

    答案:通过非阻塞I/O、单Goroutine双工模型、sync.Pool对象复用、TCP_NODELAY优化及高效心跳管理,结合系统调优,可显著提升Golang百万级TCP长连接处理效率。 在高并发网络服务场景中,TCP长连接的处理效率直接影响系统的吞吐能力和资源消耗。Golang凭借其轻量级Gor…

    2026年5月10日
    000
  • C++内存检测工具 Valgrind使用实践指南

    Valgrind是一款主要用于Linux和macOS的内存调试工具,可检测内存泄漏、越界访问、未初始化内存使用等问题,通过memcheck工具结合–leak-check=full、–track-origins=yes等选项进行详细分析,需编译时添加-g选项以支持调试信息,虽然…

    2026年5月10日
    000
  • php源码怎么运行手机_php源码手机运行环境搭建步骤【教程】

    可在手机上通过特定工具运行PHP源码。首先选择支持PHP的移动应用,安卓用户可安装UserLAnd或KSWEB,iOS用户可尝试iSH Shell或a-Shell;然后配置本地服务器环境,启动HTTP和PHP服务,将PHP文件放入指定根目录;接着可通过Termux搭建完整开发环境,更新包列表并安装P…

    2026年5月10日
    200
  • PHP动态网页数据库备份恢复_PHP动态网页MySQL数据库备份教程

    答案:PHP动态网页的MySQL数据库备份与恢复需通过定期导出SQL文件并安全存储来保障数据安全,核心方法包括使用mysqldump命令行工具实现高效灵活的自动化备份,利用phpMyAdmin图形化工具进行手动导出导入以降低操作门槛,以及通过PHP脚本调用系统命令将备份过程集成到应用中;恢复时可采用…

    2026年5月10日
    000
  • 解决Go语言中GOPATH未设置错误及工作区配置指南

    本文旨在解决go语言开发中常见的“gopath not set”错误,并提供详细的go工作区配置指南。内容涵盖`gopath`环境变量的设置、go项目目录结构、`path`变量的扩展,以及一些高级配置技巧,旨在帮助开发者建立一个高效、规范的go开发环境,确保包的下载、编译和运行顺利进行。 Go语言在…

    2026年5月10日
    000
  • Go语言中sync.WaitGroup的深度解析与实践

    sync.WaitGroup是Go语言中用于并发编程的重要同步原语,它允许主协程等待一组子协程执行完毕。本文将深入探讨WaitGroup的工作原理、典型使用模式及其与sync.Mutex等其他同步机制的区别,并通过实际代码示例,帮助读者掌握其在并发控制中的应用,避免常见的误区,确保并发程序的正确性和…

    2026年5月10日
    000
  • Linux用scp命令上传HTML文件到远程服务器

    使用scp命令可安全上传HTML文件至远程服务器:1、上传单个文件需指定源路径与目标地址;2、批量上传可用*.html通配符;3、递归上传目录需加-r参数;4、非默认端口需用-P指定端口号,依次执行并输入密码即可完成传输。 如果您需要将本地的HTML文件上传到远程服务器,并且拥有SSH访问权限,可以…

    2026年5月10日
    000
  • c++如何调用系统命令_c++执行系统命令方法

    使用std::system()可执行系统命令,需包含cstdlib头文件,传入命令字符串,返回值表示执行结果。示例:Linux下用”ls -l”列出文件,Windows下用”dir”。返回0表示成功,非0表示失败,可用于判断命令执行状态。注意跨平台命令…

    2026年5月10日
    200
  • 跨平台 C++ 代码中设计模式的移植问题与解决方案

    在跨平台 c++++ 开发中,设计模式移植问题包括:平台依赖性、头文件可用性、命名冲突、内存管理。解决方案包括使用跨平台库、预处理器指令、命名空间、跨平台内存管理库等。 跨平台 C++ 代码中设计模式的移植问题与解决方案 在跨平台 C++ 开发中,将设计模式从一个平台移植到另一个平台时,可能会遇到一…

    2026年5月10日
    000
  • C++怎么使用静态库和动态库_C++链接静态库与动态库的方法与区别

    静态库在编译时链接,生成独立可执行文件;动态库运行时加载,节省内存。1. 静态库用ar打包.o文件为.a,编译时通过-L和-l链接;2. 动态库需-fPIC编译生成.so,运行前配置LD_LIBRARY_PATH或系统路径;3. 静态库体积大但部署方便,动态库共享内存利于更新。 在C++项目开发中,…

    2026年5月10日
    000

发表回复

登录后才能评论
关注微信