Mutex 系列类(四种)
- std::mutex,最基本的 Mutex 类
独占互斥量,只能加锁一次
std::mutex 是C++11 中最基本的互斥量,std::mutex 对象提供了独占所有权的特性——即不支持递归地对 std::mutex 对象上锁,而 std::recursive_lock 则可以递归地对互斥量对象上锁。
std::mutex成员函数:
- 构造函数,std::mutex不允许拷贝构造,也不允许 move 拷贝,最初产生的 mutex 对象是处于 unlocked 状态的。
lock()
,调用线程将锁住该互斥量unlock()
, 解锁,释放对互斥量的所有权。try_lock()
,尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被阻塞。
- std::recursive_timed_mutex,递归 Mutex 类
递归的独占互斥量,允许同一个线程,同一个互斥量,多次被lock,用法和非递归的一样 跟windows的临界区是一样的,但是调用次数是有上限的,效率也低一些
和 std::mutex 不同的是,
std::recursive_mutex 允许同一个线程对互斥量多次上锁(即递归上锁),来获得对互斥量对象的多层所有权,
std::recursive_mutex 释放互斥量时需要调用与该锁层次深度相同次数的 unlock(),可理解为 lock() 次数和 unlock() 次数相同.
- std::timed_mutex,定时 Mutex 类。
带超时的互斥量,独占互斥量 这个就是拿不到锁会等待一段儿时间,但是超过设定时间,就继续执行
std::time_mutex 比 std::mutex 多了两个成员函数,try_lock_for(),try_lock_until()。
template< class Rep, class Period > bool try_lock_for( const std::chrono::duration<Rep,Period>& timeout_duration ); // (C++11 起) template< class Rep, class Period > bool try_lock_for( const std::chrono::duration<Rep,Period>& timeout_duration ); // (C++11 起)
try_lock_for
:尝试锁定互斥,若互斥在指定的时限时期中不可用则返回。try_lock_until
:尝试锁定互斥,若直至抵达指定时间点互斥不可用则返回。
参数
timeout_duration - 要阻塞的最大时长
返回值
若成功获得锁则为 true ,否则为 false 。
异常
执行期间时钟、时间点或时长可能抛出的异常(标准库提供的时钟、时间点和时长决不抛出)
- std::recursive_timed_mutex,定时递归 Mutex
带超时的,递归的,独占互斥量,允许同一个线程,同一个互斥量,多次被lock,用法和非递归的一样
Lock 类(两种)
- std::lock_guard,与 Mutex RAII 相关,方便线程对互斥量上锁。
std::lock_guard
在构造时自动锁定其管理的mutex对象,实现了std::mutex的lock功能,而在析构时则自动解锁,无须手动调用unlock,使得共享资源的访问更为安全。通过这种方式,它可以有效地保护共享数据,防止并发访问导致的数据不一致。std::lock_guard
为了防止在线程使用mutex加锁后由于异常退出导致死锁的问题,建议使用lock_guard代替mutex。这样,在某个lock_guard对象的生命周期内,它所管理的锁对象会一直保持上锁状态;而lock_guard的生命周期结束之后,它所管理的锁对象会被解锁。这一特性可以简化编程,使开发者不必担心异常或忘记解锁导致的问题。std::lock_guard
类的构造函数禁用拷贝构造,且禁用移动构造。std::lock_guard类除了构造函数和析构函数外没有其它成员函数。这种设计意图是禁止对象的复制和移动,确保锁的唯一性和安全性。这一特性也使得std::lock_guard
只能被锁定一次,并在销毁时自动解锁。- 因为
std::lock_guard
对象在创建时就锁定了mutex对象,并且在其生命周期结束时自动解锁,这使得它特别适合用于函数和小范围的代码块中。在这些场景下,当代码执行完毕或遇到return、throw等情况时,std::lock_guard
对象的生命周期结束,从而自动释放锁,大大增强了代码的健壮性。注意,lock_guard对象并不负责管理mutex对象的生命周期。互斥对象的生存期至少要延长到锁它的lock_guard被销毁。这一点对于正确使用std::lock_guard至关重要。它只负责锁的获取和释放,对于互斥量对象本身的创建和销毁并不负责。这样可以更加灵活地管理互斥量的生命周期。
- std::unique_lock,与 Mutex RAII 相关,方便线程对互斥量上锁,但提供了更好的上锁和解锁控制。
unique_lock
是个类模板,工作中,一般lock_guard(推荐使用);lock_guard取代了mutex的lock()和unlock();unique_lock
比lock_guard
灵活很多,效率上差一点,内存占用多一点。std::unique_lock
可以在构造时传递第二个参数用于管理互斥量,且能传递不同域中互斥量所有权。unique_lock
私有成员为指针 _Mutex /*_Pmtx,指向传递进来的互斥量,lock_guard私有成员为引用_Mutex& _MyMutex,引用传递进的互斥量。这就决定了unique_lock能够实现传递互斥量的功能。unique_lock
的用法比较多,如果对锁的需求比较简单推荐使用lock_guard。当需要超时或者手动解锁等功能,可以考虑使用unique_lock
不同的情况可使用对应的构造创建对象
unique_lock(mutex, adopt_lock_t) //传递被使用过的mutex,且已经被上过锁,通过。无上锁动作,不阻塞。 unique_lock(mutex, defer_lock_t) //传递被使用过的mutex,未被上过锁。无上锁动作,不阻塞。 unique_lock(mutex, try_to_lock_t) //任何状态的mutex。尝试上锁,不阻塞。 unique_lock(_Mutex& _Mtx, const chrono::duration<_Rep, _Period>& _Rel_time) //在指定时间长内尝试获取传递的mutex的锁返回。若无法获取锁,会阻塞到指定时间长。 unique_lock(mutex_type& m,std::chrono::time_point<Clock,Duration> const& absolute_time) //在给定时间点尝试获取传递的mutex锁返回。若无法获取锁,会阻塞到指定时间点。 unique_lock(unique_lock&& _Other) //将已经创建的unique_lock锁的所有权转移到新的锁。保持之前锁的状态,不阻塞 ```
unique_lock的成员函数
unique_lock
除了拥有跟std::mutex一样的三个成员函数意外,还提供release()
函数。
release()返回它所管理的mutex对象指针,并释放所有权;也就是说,这个unique_lock和mutex不再有关系。严格区分unlock()与release()的区别,不要混淆。
std::unique_lock<std::mutex> sbguard(my_mutex); std::mutex *ptx = sbguard.release(); //现在你有责任自己解锁了 msgRecvQueue.push_back(i); ptx->unlock(); //自己负责mutex的unlock了
unique_lock所有权的传递
std::unique_lockstd::mutex sbguard(my_mutex);//所有权概念 std::unique_lockstd::mutex sbguard1(my_mutex); std::unique_lockstd::mutex sbguard2(sbguard1);//此句是非法的,复制所有权是非法 //sbguard拥有my_mutex的所有权;sbguard可以把自己对mutex(my_mutex)的所有权转移给其他的unique_lock对象; //所以unique_lock对象这个mutex的所有权是可以转移,但是不能复制。
所有权的传递的方法
方法1 :std::move()
方法2:return std:: unique_lockstd::mutex
其他类型
- std::once_flag(提供一种方式,可以保证其实例绑定的函数,能且仅能被执行一次)
std::once_flag是一个用于与std::call_once函数配合使用的标志类型。它可以确保一个函数在多线程环境中仅被调用一次。这在某些情况下非常有用,例如在单例模式中或在初始化全局资源时。
std::once_flag本身没有任何成员函数或公共接口。它仅仅是一个标识,用于记录一个给定的函数是否已经被调用。std::once_flag的一个实例应该与需要仅执行一次的函数绑定。为了防止多个线程竞争执行这个函数,您可以将std::once_flag与std::call_once一起使用。
- adopt_lock_t(假设调用方线程已拥有互斥的所有权)
- Value used as possible argument to the constructor of unique_lock or lock_guard.
- objects constructed with adopt_lock do not lock the mutex object on construction, assuming instead that it is already locked by the current thread.
- The value is a compile-time constant that carries no state, and is merely used to disambiguate between constructor signatures.
- adopt_lock_t is an empty class.
- std::defer_lock_t(不获得互斥的所有权)
- Value used as possible argument to unique_lock’s constructor.
- unique_lock objects constructed with defer_lock do not lock the mutex object automatically on construction, initializing them as not owning a lock.
- The value is a compile-time constant that carries no state, and is merely used to disambiguate between constructor signatures.
- defer_lock_t is an empty class.
- std::try_to_lock_t(尝试获得互斥的所有权而不阻塞)
- Value used as possible argument to unique_lock’s constructor.
- unique_lock objects constructed with try_to_lock attempt to lock the mutex object by calling its try_lock member instead of its lock member.
- The value is a compile-time constant that carries no state, and is merely used to disambiguate between constructor signatures.
相关函数
std::try_lock,尝试同时对多个互斥量上锁。
std::lock,可以同时对多个互斥量上锁。
std::call_once,如果多个线程需要同时调用某个函数,call_once 可以保证多个线程对该函数只调用一次。
template <class Fn, class... Args> void call_once (once_flag&flag, Fn&& fn, Args&&... args);
flag
:仅可执行一次的对象,其生命周期要比所在的线程长
Fn
:执行函数
args
:执行函数的可变参数当多个线程使用同一个flag对象去调用函数call_once时,仅有一个线程可以真正的完成对函数Fn的调用,其他的线程会被阻塞,直到函数Fn返回.
综合示例
#include <iostream> #include <vector> #include <thread> #include <mutex> #include <chrono> #include <condition_variable> #include <functional> class TaskQueue { public: void push(int task) { { std::unique_lock<std::mutex> lock(mutex_); tasks_.push_back(task); } cond_.notify_one(); } int pop() { std::unique_lock<std::mutex> lock(mutex_); cond_.wait(lock, [this] { return !tasks_.empty(); }); int task = tasks_.front(); tasks_.erase(tasks_.begin()); return task; } private: std::vector<int> tasks_; std::mutex mutex_; std::condition_variable cond_; }; std::once_flag flag; void run_once() { std::cout << "Run once." << std::endl; } void worker(TaskQueue& taskQueue) { std::call_once(flag, run_once); while (true) { int task = taskQueue.pop(); if (task == -1) { break; } std::cout << "Processing task: " << task << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(100)); } } int main() { TaskQueue taskQueue; std::vector<std::thread> threads; for (int i = 0; i < 4; ++i) { threads.push_back(std::thread(worker, std::ref(taskQueue))); } for (int i = 0; i < 10; ++i) { taskQueue.push(i); } for (int i = 0; i < 4; ++i) { taskQueue.push(-1); } for (auto& thread : threads) { thread.join(); } return 0; }
在这个示例中,我们使用了以下组件:
std::mutex用于保护TaskQueue的内部数据结构。
std::unique_lock用于自动上锁和解锁互斥量。
std::condition_variable用于线程间的通知。
std::once_flag和std::call_once组合使用,确保run_once()函数只被执行一次。
注意,由于示例的简单性,我们没有使用到std::recursive_mutex、std::timed_mutex、std::recursive_timed_mutex和std::lock_guard。
这些类在不同的场景下可以用于替换示例中的std::mutex和std::unique_lock。