C++ 互斥锁原理以及实际使用介绍

article/2025/9/17 17:23:42

        兄弟姐妹们,我又回来了,今天带来实际开发中都需要使用的互斥锁的内容,主要聊一聊如何使用互斥锁以及都有哪几种方式实现互斥锁。实现互斥,可以有以下几种方式:互斥量(Mutex)、递归互斥量(Recursive Mutex)、读写锁(Read-Write Lock)、条件变量(Condition Variable)。

目录

一、互斥原理(mutex)

二、递归互斥量(Recursive Mutex)

三、读写锁(Read-Write Lock)

四、条件变量(Condition Variable)

五、总结

一、互斥原理(mutex)

        互斥锁可以确保在任何时候只有一个线程能够进入临界区。当线程需要进入临界区时,它会尝试获取互斥锁的所有权,如果互斥锁已经被其他线程占用,那么当前线程就会进入阻塞状态,直到互斥锁被释放为止。简单说就是一块区域只能被一个线程执行。

        当一个线程获取到互斥锁的所有权后,它就可以进入临界区进行操作,当操作完成后,它需要释放互斥锁,让其他线程有机会进入临界区。

        下面是一个简单的互斥锁的示例代码,它演示了如何使用 std::mutex 类来保护临界区:


#include <iostream>
#include <thread>
#include <mutex>// 定义互斥锁
std::mutex g_mutex;// 临界区代码
void critical_section(int thread_id) {// 加锁g_mutex.lock();// 访问共享资源std::cout << "Thread " << thread_id << " enter critical section." << std::endl;// 释放锁std::this_thread::sleep_for(std::chrono::seconds(5));g_mutex.unlock();
}int main() {// 创建两个线程std::thread t1(critical_section, 1);std::thread t2(critical_section, 2);// 等待两个线程执行完成t1.join();t2.join();return 0;
}

        main中创建两个线程去访问资源,但是其中一个需要等待另一个线程5s释放后才能访问,形成对资源的锁定。

        上面的例子使用的是std::mutex实现互斥锁,需要注意这个互斥锁的声明需要相对的全局变量,也就是说对于使用锁的部分它必须是“全局的”。

二、递归互斥量(Recursive Mutex)

        C++ 中的递归互斥量(Recursive Mutex)是一种特殊的互斥量,它可以被同一个线程多次锁定,而不会发生死锁。递归互斥量的实现原理是,在锁定时维护一个锁定计数器,每次解锁时将计数器减一,只有当计数器为 0 时才会释放锁。

        以下是递归互斥量的示例代码:

#include <iostream>
#include <thread>
#include <mutex>std::recursive_mutex mtx;void foo(int n) {mtx.lock();std::cout << "Thread " << n << " locked the mutex." << std::endl;if (n > 1) {foo(n - 1);}std::cout << "Thread " << n << " unlocked the mutex." << std::endl;mtx.unlock();
}int main() {std::thread t1(foo, 3);std::thread t2(foo, 2);t1.join();t2.join();return 0;
}

        在上面的代码中,我们定义了一个递归函数 foo(),它接受一个整数参数 n,表示当前线程的编号。在函数中,我们首先使用递归互斥量 mtx 锁定当前线程,然后输出一条带有线程编号的信息,接着判断如果 n 大于 1,则递归调用 foo() 函数,并将参数减一。最后,我们输出一条解锁信息,并将递归互斥量解锁。

        在主函数中,我们创建了两个线程 t1 和 t2,分别调用 foo() 函数,并传入不同的参数值。由于递归互斥量可以被同一个线程多次锁定,因此在 t1 线程中对 mtx 进行了两次锁定,而在 t2 线程中只进行了一次锁定。

        运行结果:

        可以看到,递归互斥量可以被同一个线程多次锁定,并且在解锁时必须对应减少锁定计数器。这种机制可以避免死锁的发生,但也需要注意使用时的线程安全问题。

三、读写锁(Read-Write Lock)

        读写锁(Read-Write Lock)是一种特殊的互斥锁,用于在多线程环境下对共享资源进行读写操作。它允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。读写锁的使用可以提高并发性能,特别是当读操作比写操作频繁时。

        在 C++ 中,读写锁可以通过 std::shared_mutex 类型来实现。下面是一个简单的示例代码,演示了如何使用读写锁来保护一个共享的整型变量:

#include <iostream>
#include <thread>
#include <chrono>
#include <shared_mutex>std::shared_mutex rw_lock; // 读写锁
int shared_var = 0; // 共享变量// 写线程函数
void writer() {for (int i = 0; i < 10; ++i) {// 独占写锁std::unique_lock<std::shared_mutex> lock(rw_lock);// 写共享变量++shared_var;std::cout << "Writer thread: write shared_var=" << shared_var << std::endl;// 等待一段时间std::this_thread::sleep_for(std::chrono::milliseconds(100));}
}// 读线程函数
void reader(int id) {for (int i = 0; i < 10; ++i) {// 共享读锁std::shared_lock<std::shared_mutex> lock(rw_lock);// 读共享变量int value = shared_var;std::cout << "Reader thread " << id << ": read shared_var=" << value << std::endl;// 等待一段时间std::this_thread::sleep_for(std::chrono::milliseconds(100));}
}int main() {std::thread t1(writer);std::thread t2(reader, 1);std::thread t3(reader, 2);std::thread t4(reader, 3);t1.join();t2.join();t3.join();t4.join();return 0;
}

        在上面的代码中,我们定义了一个共享变量 shared_var 和一个读写锁 rw_lock。写线程函数 writer() 独占写锁,对 shared_var 进行自增操作,并输出当前的值。读线程函数 reader() 共享读锁,读取 shared_var 的值,并输出当前的值。所有的线程都会等待一段时间,以模拟实际的操作。

        在主函数中,我们创建了一个写线程和三个读线程。由于读写锁的特性,读线程可以并发读取共享变量,而写线程会独占写锁,只有在写操作完成之后,读线程才能再次读取共享变量。因此,输出结果中读线程的顺序可能会有所不同,但是写线程的操作一定是顺序执行的。

        注意,这里使用 std::unique_lockstd::shared_mutex 类型的对象来获取独占写锁,使用 std::shared_lockstd::shared_mutex 类型的对象来获取共享读锁。这些锁对象会在作用域结束时自动解锁,避免了手动解锁的问题。

四、条件变量(Condition Variable)

        条件变量(Condition Variable)是一种线程间同步机制,用于在某些特定条件下阻塞或唤醒线程。在 C++ 中,条件变量是通过 std::condition_variable 类来实现的。

        下面是一个使用条件变量的示例代码,其中有两个线程,一个线程不停地生产数据,另一个线程则等待数据,当有数据可用时,将数据进行消费。

#include <iostream>
#include <thread>
#include <chrono>
#include <queue>
#include <mutex>
#include <condition_variable>std::queue<int> data_queue; // 数据队列
std::mutex data_mutex; // 互斥锁
std::condition_variable data_cond; // 条件变量// 生产数据函数
void producer() {for (int i = 1; i <= 10; ++i) {// 生产数据std::this_thread::sleep_for(std::chrono::milliseconds(100));std::unique_lock<std::mutex> lock(data_mutex);data_queue.push(i);std::cout << "Producer thread: produce data " << i << std::endl;// 唤醒消费线程data_cond.notify_one();}
}// 消费数据函数
void consumer() {while (true) {// 等待数据std::unique_lock<std::mutex> lock(data_mutex);data_cond.wait(lock, [] { return !data_queue.empty(); });// 消费数据int data = data_queue.front();data_queue.pop();std::cout << "Consumer thread: consume data " << data << std::endl;// 检查是否结束if (data == 10) {break;}}
}int main() {std::thread t1(producer);std::thread t2(consumer);t1.join();t2.join();return 0;
}

        在上面的代码中,我们定义了一个数据队列 data_queue 和一个互斥锁 data_mutex,同时定义了一个条件变量 data_cond。生产数据的函数 producer() 不停地往队列中添加数据,每次添加完数据之后,通过调用 data_cond.notify_one() 唤醒等待的消费线程。消费数据的函数 consumer() 通过调用 data_cond.wait(lock, [] { return !data_queue.empty(); }) 来等待数据,当队列中有数据时,将数据从队列中取出并消费,如果取出的数据是最后一个,则退出循环。

        在主函数中,我们创建了一个生产线程和一个消费线程。生产线程生产 10 个数据,消费线程从队列中消费数据,直到消费到最后一个数据为止。

        注意,这里使用了 std::unique_lockstd::mutex 类型的对象来获取互斥锁,并使用 lambda 表达式 [] { return !data_queue.empty(); } 来判断条件是否满足。在调用 wait() 函数时,当前线程会阻塞,直到条件变量被其他线程唤醒或超时。当 wait() 函数返回时,当前线程会重新获取互斥。

简单一些的例子:

#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
#include <condition_variable>bool ready = false; // 条件变量
std::mutex data_mutex; // 互斥锁
std::condition_variable data_cond; // 条件变量void do_something() {// 模拟工作std::this_thread::sleep_for(std::chrono::milliseconds(500));
}void waiting_thread() {// 等待条件变量std::unique_lock<std::mutex> lock(data_mutex);data_cond.wait(lock, [] { return ready; });// 条件满足后输出一句话std::cout << "Condition satisfied, waiting thread resumes." << std::endl;do_something();
}int main() {std::thread t1(waiting_thread);// 模拟条件满足后的操作std::this_thread::sleep_for(std::chrono::milliseconds(1000));{std::lock_guard<std::mutex> lock(data_mutex);ready = true;data_cond.notify_one();}t1.join();return 0;
}

        在上面的代码中,我们定义了一个条件变量 ready 和一个互斥锁 data_mutex,同时定义了一个条件变量 data_cond。等待条件变量的函数 waiting_thread() 首先获取互斥锁,然后通过调用 data_cond.wait(lock, [] { return ready; }) 等待条件变量,当 ready 为 true 时,线程会被唤醒,输出一句话,并模拟一些工作的操作。在主函数中,我们创建了一个等待条件变量的线程 t1,然后模拟条件满足后的操作,即将 ready 设置为 true,然后通过调用 data_cond.notify_one() 唤醒等待的线程。 

五、总结

        互斥锁保证了计算机资源访问的安全,互斥锁的不当使用同时也加大了程序阻塞的风险。

        提前祝大家五一前工作生活学习一切顺利。


http://chatgpt.dhexx.cn/article/iPpPIjbo.shtml

相关文章

MySQL mutex互斥锁

在事务机制中&#xff0c;锁机制是为了保证高并发&#xff0c;数据一致性的重要实现方式。MySQL除了Innodb引擎层面的行锁&#xff0c;还有latch锁。latch锁的作用资源协调处理。包含表句柄&#xff0c;线程&#xff0c;cpu线程&#xff0c;内存池等。其保证非常短时间内快速处…

uCOSii中的互斥信号量

uCOSii中的互斥信号量 一、互斥型信号量项管理 (MUTUAL EXCLUSION SEMAPHORE MANAGEMENT) OSMutexAccept() 无条件等待地获取互斥型信号量 OSMutexCreate() 建立并初始化一个互斥型信号量 OSMutexDel() 删除互斥型信号量 OSMutexPend() 等待一个互斥型信号量 OSMutexPost…

互斥信号量

目录 1、Creat 2、Delete 3、Wait 4、Post 5、Statu 互斥信号量 在介绍二进制信号量时&#xff0c;曾讨论到如果二进制信号量创建时设置参数 bInitValue 为TRUE&#xff0c;则可以用于互斥访问共享资源。实际上&#xff0c;SylixOS 的二进制信号量实现的互斥性是将一个变量…

UCOS-III 互斥量

互斥量 一、互斥量基本概念二、互斥量优先级继承机制三、互斥量应用场景四、互斥量运作机制五、互斥量创建流程1、定义互斥量2、创建互斥量 六、互斥量接口函数1、创建互斥量函数OSMutexCreate()2、删除互斥量函数 OSMutexDel()3、获取互斥量函数 OSMutexPend()4、释放互斥量函…

互斥量知识

文章目录 互斥量1、基本概念2、互斥量的优先级继承机制3、互斥量应用场景4、互斥量运行机制5、互斥量控制块6、互斥量函数接口&#xff08;1&#xff09;互斥量创建函数 xSemaphoreCreateMutex()&#xff08;2&#xff09;递归互斥量创建函数 xSemaphoreCreateRecursiveMutex()…

同步和互斥

同步和互斥 竞争与协作 在单核 CPU 系统里&#xff0c;为了实现多个程序同时运行的假象&#xff0c;操作系统通常以时间片调度的方式&#xff0c;让每个进程执行每次执行一个时间片&#xff0c;时间片用完了&#xff0c;就切换下一个进程运行&#xff0c;由于这个时间片的时间很…

多线程的同步与互斥(互斥锁、条件变量、读写锁、自旋锁、信号量)

文章目录 一、同步与互斥的概念二、互斥锁&#xff08;同步&#xff09;三、条件变量&#xff08;同步&#xff09;1、线程的条件变量实例12、线程的条件变量实例23、虚假唤醒(spurious wakeup) 四、读写锁&#xff08;同步&#xff09;五、自旋锁&#xff08;同步&#xff09;…

同步和互斥区别

互斥的概念 由于多线程执行操作共享变量的这段代码可能会导致竞争状态&#xff0c;因此我们将此段代码称为临界区&#xff08;critical section&#xff09;&#xff0c;它是访问共享资源的代码片段&#xff0c;一定不能给多线程同时执行。 我们希望这段代码是互斥&#xff0…

操作系统——互斥的定义及实现

一、进程互斥的定义 所谓进程互斥,指的是对某个系统资源,一个进程正在使用它,另外一个想用它的进程就必须等待,而不能同时使用 。进程互斥是多道程序系统中进程间存在的一种源于资源共享的制约关系,也称间接制约关系,主要是由被共享资源的使用性质所决定的。 二、互斥…

Fisher判别分析详解

Fisher判别分析 将高维度空间的样本投影到低维空间上&#xff0c;使得投影后的样本数据在新的子空间上有最小的类内距离以及最大的类间距离&#xff0c;使得在该子空间上有最佳的可分离性 可以看出右侧投影后具有更好的可分离性。 Fisher判别分析和PCA差别 刚学完感觉两个很…

基于spss的多元统计分析 之 聚类分析+判别分析(2/8)

实验目的&#xff1a; 1&#xff0e;掌握聚类分析及判别分析的基本原理&#xff1b; 2&#xff0e;熟悉掌握SPSS软件进行聚类分析及判别分析的基本操作&#xff1b; 3&#xff0e;利用实验指导的实例数据&#xff0c;上机熟悉聚类分析及判别分析方法。 实验前预习&#xff1a;…

机器学习——线性判别分析

目录 线性判别分析 LDA的降维过程 案例&#xff1a;鸢尾花(Iris) 代码演示 数据集 局部线性嵌入 线性判别分析 线性判别分析&#xff08;LDA&#xff09;是一种有监督的线性降维算法。与PCA不同&#xff0c;LDA是为了使降维后的数据点尽可能地容易被区分。 线性判别分析…

MATLAB判别分析例题,判别分析的matlab实现案例.doc

判别分析的matlab实现案例.doc 读取EXAMP10_01XLS中数据&#xff0c;进行距离判别读取数据读取文件EXAMP10_01XLS的第1个工作表中C2F51范围的数据&#xff0c;即全部样本数据&#xff0c;包括未判企业SAMPLEXLSREAD EXAMP10_01XLS , , C2F51 读取文件EXAMP10_01XLS的第1个工作…

SAS数据分析之判别分析

判别分析与聚类分析有非常类似的特性&#xff0c;因此&#xff0c;在多数数据分析的教材中&#xff0c;这两章是一前一后出现的&#xff0c;简而言之&#xff0c;聚类分析&#xff0c;其实是判别分析的基础&#xff0c;即在聚类分析的基础上&#xff0c;总结出各类的权值&#…

线性判别分析

线性判别分析&#xff08;LDA&#xff09;是一种经典的线性学习方法。其思想是&#xff1a;给定训练样例集&#xff0c;设法将样例投影到一条直线上&#xff0c;使得同类样例的投影点尽可能接近、异类样例的投影点尽可能远离。如图所示的二分类示意图&#xff1a; 损失函数的…

sas判别分析

#判别分析两大类&#xff1a;Fisher&Bayes #neighbbor&#xff1a;马氏距离和欧氏距离&#xff1b; #典型判别分析&#xff0c;联系典型相关分析#组变量相关&#xff0c;最后得到的每组内变量的线性组合作为典型变量 #协方差矩阵&#xff0c;举个栗子,设一组特征x(a1,a…

【数模】判别分析

文章目录 判别分析简介SPSS操作步骤输出结果分析 判别分析简介 判别分析又称“分辨法”&#xff0c;是在分类确定的条件下&#xff0c;根据某一研究对象的各种特征值判别其类型归属问题的一种多变量统计分析方法。 其基本原理是按照一定的判别准则&#xff0c;建立一个或多个判…

fisher线性判别分析matlab,线性判别分析LDA

首先搞清楚什么叫判别分析&#xff1f;Discriminant Analysis就是根据研究对象的 各种特征值判别其类型归属问题的一种多变量统计分析方法。 根据判别标准不同&#xff0c;可以分为距离判别、Fisher判别、Bayes判别法等。比如在KNN中用的就是距离判别&#xff0c;当然这里的“距…

spssfisher判别分析步骤_spss进行判别分析步骤_spss进行判别分析

1.Discriminant Analysis判别分析主对话框 如图 1-1 所示 图 1-1 Discriminant Analysis 主对话框 (1)选择分类变量及其范围 在主对话框中左面的矩形框中选择表明已知的观测量所属类别的变量(一定是离散变量), 按上面的一个向右的箭头按钮,使该变量名移到右面的Groupin…

R语言判别分析

本文首发于公众号&#xff1a;医学和生信笔记&#xff0c;完美观看体验请至公众号查看本文。 文章目录 Fisher判别分析Bayes判别分析 判别分析&#xff08;discriminant analysis&#xff09;是根据判别对象若干个指标的观测结果判定其属于哪一类的统计方法。经典的判别分析方法…