news 2026/9/2 12:26:32

C++多线程:互斥锁

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++多线程:互斥锁

1.前言

比如说我们现在以一个list容器来模仿一个消息队列,当消息来临时插入list的尾部,当读取消息时就把头部的消息读出来并且删除这条消息。在代码中就以两个线程分别实现消息写入和消息读取的功能,如下:

class msgList { private: list<int>mylist; //用list模仿一个消息队列 public: void WriteList() //向消息队列中写入消息(以i作为消息) { for (int i = 0; i<100000; i++) { cout << "Write : " << i <<endl; mylist.push_back(i); } return; } void ReadList() //从消息队列中读取并取出消息 { for(int i=0;i<100000;i++) { if (!mylist.empty()) { cout << "Read : " << mylist.front() << endl; mylist.pop_front(); } else { cout << "Message List is empty!" << endl; } } } }; int main() { msgList mlist; thread pread(&msgList::ReadList, &mlist); //读线程 thread pwrite(&msgList::WriteList, &mlist); //写线程 //等待线程结束 pread.join(); pwrite.join(); return 0; }

这段程序在运行过程中,大部分时间是正常的,但是也会出现如下不稳定的情况:

为什么会出现这种情况呢?

这是因为消息队列对于读线程和写线程来说是共享的,这时就会出现两种特殊的情况:读线程的读取操作还没有结束,线程上下文就切换到了写线程中;或者写线程的写入操作还没有结束,线程上下文切换就到了读线程中,这两种情况都反映了读写冲突,从而出现了以上错误。

要想解决这个问题,最显然最直接的方法就是将读写操作分离开来,读的时候不允许写,写的时候不允许读,这样,才能实现线程安全的读和写。说形象一点,就是在进行读操作时,就对共享资源进行加锁,禁止其他线程访问,其他线程要访问就得等到读线程解锁才行,就像上厕所一样,一次只能上一个人,其他人必须得等他上完了再上。这样,就有了互斥锁的概念。


2 互斥锁


在多任务操作系统中,同时运行的多个任务可能都需要使用同一种资源。比如说,同一个文件,可能一个线程会对其进行写操作,而另一个线程需要对这个文件进行读操作,可想而知,如果写线程还没有写结束,而此时读线程开始了,或者读线程还没有读结束而写线程开始了,那么最终的结果显然会是混乱的。为了保护共享资源,在线程里也有这么一把锁——互斥锁(mutex),互斥锁是一种简单的加锁的方法来控制对共享资源的访问,互斥锁只有两种状态,即上锁( lock )和解锁( unlock )。

2.1 互斥锁的特点


1. 原子性:把一个互斥量锁定为一个原子操作,这意味着如果一个线程锁定了一个互斥量,没有其他线程在同一时间可以成功锁定这个互斥量;

2. 唯一性:如果一个线程锁定了一个互斥量,在它解除锁定之前,没有其他线程可以锁定这个互斥量;

3. 非繁忙等待:如果一个线程已经锁定了一个互斥量,第二个线程又试图去锁定这个互斥量,则第二个线程将被挂起(不占用任何cpu资源),直到第一个线程解除对这个互斥量的锁定为止,第二个线程则被唤醒并继续执行,同时锁定这个互斥量。

2.2 互斥锁的使用


根据前面我们可以知道,互斥锁主要就是用来保护共享资源的,在C++ 11中,互斥锁封装在mutex类中,通过调用类成员函数lock()和unlock()来实现加锁和解锁。值得注意的是,加锁和解锁,必须成对使用,这也是比较好理解的。除此之外,互斥量的使用时机,就以开篇程序为例,我们要保护的共享资源当然就是消息队列list了,那么互斥锁应该加在哪里呢?

可能想的比较简单一点:就直接把锁加在函数最前面不就好了么?如下所示:

class msgList { private: list<int>mylist; //用list模仿一个消息队列 mutex mtx; //创建互斥锁对象 public: void WriteList() //向消息队列中写入消息(以i作为消息) { mtx.lock(); for (int i = 0; i<100000; i++) { cout << "Write : " << i <<endl; mylist.push_back(i); } mtx.unlock(); return; } //....... };

不过如果这样加锁的话,要等写线程完全执行结束才能开始读线程,读写线程变成了串行执行,这就违背了线程并发性的特点了。正确的加锁方式应当是在执行写操作的具体部分加锁,如下所示:

class msgList { private: list<int>mylist; //用list模仿一个消息队列 mutex mtx; //创建互斥锁对象 public: void WriteList() //向消息队列中写入消息(以i作为消息) { for (int i = 0; i<100000; i++) { mtx.lock(); cout << "Write : " << i <<endl; mylist.push_back(i); mtx.unlock(); } return; } //....... };

这样,才能真正的实现读写互不干扰。

下面再举一个更为直观的例子,创建两个线程同时对list进行写操作:

#include <iostream> #include <list> #include <mutex> #include <thread> using namespace std; class msgList { private: list<int> mylist; mutex m; int i = 0; public: void WriteList() { while (true) { int val; { // 临界区:读判断 + 自增 + 插入,三步必须在一个锁内 lock_guard<mutex> lk(m); if (i >= 1000) break; val = i++; mylist.push_back(val); } // 出作用域自动解锁 // 锁外可做其他无需互斥的事 } } void showList() { lock_guard<mutex> lk(m); // 若可能与其他线程并发,读也要加锁 for (auto p = mylist.begin(); p != mylist.end(); ++p) cout << (*p) << " "; cout << endl; cout << "size of list : " << mylist.size() << endl; } }; int main() { msgList mlist; thread pwrite0(&msgList::WriteList, &mlist); thread pwrite1(&msgList::WriteList, &mlist); pwrite0.join(); pwrite1.join(); cout << "threads end!" << endl; mlist.showList(); //子线程结束后主线程打印list return 0; }

link

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/2 12:23:52

VS2022下LVGL 9.2.2模拟器搭建:从SDL2配置到踩坑实录

简介&#xff1a;面向嵌入式开发者和UI工程师&#xff0c;这份LVGL 9.2.2模拟器完整工程包旨在解决在Windows环境下使用VS2022编译运行LVGL模拟器的配置难题。资源包含约2000个文件&#xff0c;以C/C源码为主&#xff08;1078个.c、762个.h、51个.cpp&#xff09;&#xff0c;同…

作者头像 李华
网站建设 2026/9/2 12:23:34

HX711称重程序设计:从硬件接线到零点追踪的完整实战指南

简介&#xff1a;一份基于51单片机的HX711高精度称重程序工程包&#xff0c;面向嵌入式从业者、电子爱好者及实验室场景&#xff0c;用于解决称重传感器的微弱信号采集与稳定测量问题。程序围绕HX711芯片&#xff08;24位Σ-Δ型ADC&#xff09;编写&#xff0c;涵盖初始化配置…

作者头像 李华
网站建设 2026/9/2 12:19:34

轻量级去中心化服务发现与任务协同框架实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华