互斥量mutex

本文详细介绍了线程同步的基本概念及互斥量(mutex)的工作原理,包括标准库中提供的多种互斥量类型如std::mutex、std::recursive_mutex等,以及它们各自的使用场景和注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线程同步多个线程协调地,有序地同步使用共享资源。多线程共享进程资源,一个线程访问共享资源需要一段完整地时间才能完成其读写操作,如果在这段时间内被其他线程打断,就会产生各种不可预知的错误。协调线程按一定的规则,不受打扰地访问共享资源,保证正确性,这便是线程同步的出发点。 互斥量,是最简单的线程同步机制,也是最常用的同步策略。
一、互斥量mutex
       互斥量是一种线程同步对象,“互斥”的含义是同一时刻只能有一个线程获得互斥量。一个互斥量对应一个共享资源,互斥量状态:1.解锁状态意味着共享资源可用,2.加锁状态意味着共享资源不可用
       一个线程需要使用共享资源时,使用lock申请:1.当互斥量为解锁状态,则占用互斥量,并给互斥量加锁,占用资源(互相量为加锁状态,其他线程不能使用互斥量并等待互斥量变为解锁状态),2.如果互斥量为加锁状态,则线程等待,直到互斥量为解锁状态(其他线程使用完共享资源后会解锁互斥量,释放资源)。
下面是mutex头文件中内容:

mutex类4种
        std::mutex,最基本的 Mutex 类。
        std::recursive_mutex,递归 Mutex 类。
        std::time_mutex,定时 Mutex 类。
        std::recursive_timed_mutex,定时递归 Mutex 类。
Lock 类(两种)
        std::lock_guard,与 Mutex RAII 相关,方便线程对互斥量上锁。
        std::unique_lock,与 Mutex RAII 相关,方便线程对互斥量上锁,但提供了更好的上锁和解锁控制。
其他类型   
        std::once_flag
        std::adopt_lock_t
        std::defer_lock_t
        std::try_to_lock_t
函数
        std::try_lock,尝试同时对多个互斥量上锁。
        std::lock,可以同时对多个互斥量上锁。
std::call_once,如果多个线程需要同时调用某个函数,call_once 可以保证多个线程对该函数只调用一次。

二、meutex类的介绍

std::mutex 介绍

下面以 std::mutex 为例介绍 C++11 中的互斥量用法。

std::mutex 是C++11 中最基本的互斥量,std::mutex 对象提供了独占所有权的特性——即不支持递归地对 std::mutex 对象上锁,而 std::recursive_lock 则可以递归地对互斥量对象上锁。

std::mutex 的成员函数

            1、构造函数,std::mutex不允许拷贝构造,也不允许 move 拷贝,最初产生的 mutex 对象是处于 unlocked 状态的。

            2、lock(),调用线程将锁住该互斥量。线程调用该函数会发生下面 3 种情况:(1). 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用 unlock之前,该线程一直拥有该锁。(2). 如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住。(3). 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。

           3、unlock(), 解锁,释放对互斥量的所有权。

           4、try_lock(),尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被阻塞。线程调用该函数也会出现下面 3 种情况,(1). 如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用 unlock 释放互斥量。(2). 如果当前互斥量被其他线程锁住,则当前调用线程返回 false,而并不会被阻塞掉。(3). 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。

#include <iostream>
#include <thread>
#include <mutex>
using std::thread;
using std::mutex;
using std::cout;
using std::endl;	
mutex mtx;
void print_block(int n, char c) {
	//给互斥量上锁
	mtx.lock();
	for (int i = 0;i < n;++i) {
		cout << c;
	}
	cout << endl;
	//解锁,释放对互斥量的所有权。
	mtx.unlock();
}
int main() {
	//创造两个线程对象
	thread th1(print_block, 50, '*');
	thread th2(print_block, 50, '#');
	th1.join();
	th2.join();
	system("pause");
	return EXIT_SUCCESS;
}

三、recursive_mutex类的介绍

std::recursive_mutex 与 std::mutex 一样,也是一种可以被上锁的对象,但是和 std::mutex 不同的是,std::recursive_mutex 允许同一个线程对互斥量多次上锁(即递归上锁),来获得对互斥量对象的多层所有权,std::recursive_mutex 释放互斥量时需要调用与该锁层次深度相同次数的 unlock(),可理解为 lock() 次数和 unlock() 次数相同,除此之外,std::recursive_mutex 的特性和 std::mutex 大致相同。

四、time_mutex类的介绍

std::time_mutex 比 std::mutex 多了两个成员函数,try_lock_for(),try_lock_until()。

try_lock_for 函数接受一个时间范围,表示在这一段时间范围之内线程如果没有获得锁则被阻塞住(与 std::mutex 的 try_lock() 不同,try_lock 如果被调用时没有获得锁则直接返回 false),如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回 false。

try_lock_until 函数则接受一个时间点作为参数,在指定时间点未到来之前线程如果没有获得锁则被阻塞住,如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回 false。

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
std::timed_mutex mtx;
using std::cout;
using std::endl;
using std::thread;
void fireworks() {
	/******************************************************************
	*try_lock_for 函数接受一个时间范围,表示在这一段时间范围之内线程如果没有
	*获得锁则被阻塞住(与 std::mutex 的 try_lock() 不同,try_lock 如果被调
	*用时没有获得锁则直接返回 false),如果在此期间其他线程释放了锁,则该线程可
	*以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回 false。
	*******************************************************************/
	while (!mtx.try_lock_for(std::chrono::milliseconds(300))) {
		cout << "-";//等待获取互斥锁期,每300ms输出字符‘-’
	}
	//获取到互斥锁时,此线程睡眠1s之后输出字符“*”并换行  
	std::this_thread::sleep_for(std::chrono::seconds(1));
	cout << "*\n";
	//解锁
	mtx.unlock();
}
int main() {
	//创建线程对象数组
	thread threads[10];
	for (int i = 0;i < 10;++i) {
		threads[i] = thread(fireworks);
	}
	//这个用法是c++11的新特性:范围for,对于threads中的每个th执行th.join()操作
	for (auto &th : threads)
		th.join();
	system("pause");
	return EXIT_SUCCESS;
}

五、std::recursive_timed_mutex类的介绍

和 std:recursive_mutex 与 std::mutex 的关系一样,std::recursive_timed_mutex 的特性也可以从 std::timed_mutex 推导出来,感兴趣的同鞋可以自行查阅

参考:http://blog.youkuaiyun.com/chenxun_2010/article/details/49786263
http://blog.youkuaiyun.com/lovecodeless/article/details/24885127


### 如何确保互斥在超时后被正确释放 为了确保互斥 `mutex` 在尝试获取锁的过程中发生超时时能够正确处理并最终被释放,通常有几种方法可以实现这一目标。以下是具体的方法: #### 使用带超时机制的锁定操作 C++标准库提供了带有超时特性的互斥类,如 `std::timed_mutex` 和 `std::recursive_timed_mutex`。这些类型的互斥允许调用方指定等待时间,在这段时间内如果未能成功获取锁,则返回失败状态而不阻塞线程。 ```cpp #include <iostream> #include <thread> #include <chrono> #include <mutex> int main() { std::timed_mutex mtx; bool locked = mtx.try_lock_for(std::chrono::seconds(2)); if (locked) { // 成功获得了锁 try { // 执行临界区代码 } catch (...) { // 处理异常情况 } // 确保离开作用域前解锁 mtx.unlock(); } else { // 超时未获得锁 std::cout << "Failed to acquire lock within timeout period." << std::endl; } return 0; } ``` 这种方法通过设置合理的超时期限来防止死锁的发生,并且即使发生了超时也不会影响程序正常运行[^3]。 #### 利用智能指针自动管理资源 另一种更安全的方式是借助 C++ 的 RAII(Resource Acquisition Is Initialization)原则,即使用像 `std::unique_lock` 这样的工具来自动生成和销毁锁对象。当 `std::unique_lock` 析构时会自动释放它所持有的任何锁,从而减少了手动管理的风险。 ```cpp #include <iostream> #include <thread> #include <chrono> #include <mutex> void critical_section_with_timeout(std::timed_mutex& tm, int id) { using namespace std::literals::chrono_literals; std::unique_lock<std::timed_mutex> lck(tm, std::defer_lock); if (!lck.try_lock_for(1s)) { std::cout << "Thread " << id << ": Failed acquiring the mutex\n"; return; } // 锁已持有,执行受保护的操作... std::this_thread::sleep_for(50ms); // 自动释放锁,因为 unique_lock 对象即将超出范围 } int main(){ std::timed_mutex m; const unsigned thread_count = 4u; std::vector<std::jthread> threads; for(unsigned i=0;i<thread_count;++i){ threads.emplace_back(critical_section_with_timeout,std::ref(m),i+1); } for(auto &t : threads){ t.join(); } return 0; } ``` 这种方式不仅简化了代码逻辑,还提高了安全性,因为它能保证无论何时退出临界区域都会正确地释放锁[^5]。 #### 结合条件变进行复杂控制 有时可能需要更加复杂的同步行为,比如在一个特定条件下才继续执行后续操作。此时可考虑结合条件变一起工作,以达到更好的效果。不过这已经超出了单纯讨论互斥超时的问题范畴。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

~青萍之末~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值