C++ 互斥

mutex

mutex 类是能用于保护共享数据免受从多个线程同时访问的同步原语。
mutex 提供排他性非递归所有权语义:

  • 调用方线程从它成功调用 lock 或 try_lock 开始,到它调用 unlock 为止占有 mutex 。
  • 线程占有 mutex 时,所有其他线程若试图要求 mutex 的所有权,则将阻塞(对于 lock 的调用)或收到 false 返回值(对于 try_lock )。
  • 调用方线程在调用 lock 或 try_lock 前必须不占有 mutex 。
  • 若 mutex 在仍为任何线程所占有时即被销毁,或在占有 mutex 时线程终止,则行为未定义。
  • mutex 类满足互斥体 (Mutex) 和标准布局类型 (StandardLayoutType) 的全部要求。

std::mutex 既不可复制亦不可移动。

成员类型
	native_handle_type(可选)	实现定义
成员函数
	(构造函数)	构造互斥(公开成员函数)
	(析构函数) 	销毁互斥(公开成员函数)
	operator=[被删除]	不可复制赋值(公开成员函数)
锁定
	lock	锁定互斥,若互斥不可用则阻塞(公开成员函数)
	try_lock	尝试锁定互斥,若互斥不可用则返回(公开成员函数)
	unlock	解锁互斥(公开成员函数)
原生句柄
	native_handle	返回底层实现定义的原生句柄(公开成员函数)

注意:通常不直接使用 std::mutex :std::unique_lock 、 std::lock_guard 或 std::scoped_lock (C++17 起)以更加异常安全的方式管理锁定。

recursive_mutex

recursive_mutex 类是同步原语,能用于保护共享数据免受从个多线程同时访问。
recursive_mutex 提供排他性递归所有权语义:

  • 调用方线程在从它成功调用 lock 或 try_lock 开始的时期里占有 recursive_mutex 。此时期间,线程可以进行对 lock 或 try_lock 的附加调用。所有权的时期在线程调用 unlock 匹配次数时结束。
  • 线程占有 recursive_mutex 时,若其他所有线程试图要求 recursive_mutex 的所有权,则它们将阻塞(对于调用 lock )或收到 false 返回值(对于调用 try_lock )。
  • 可锁定 recursive_mutex 次数的最大值是未指定的,但抵达该数后,对 lock 的调用将抛出std::system_error 而对 try_lock 的调用将返回 false 。

若 recursive_mutex 在仍为某线程占有时被销毁,则程序行为未定义。 recursive_mutex 类满足互
斥体 (Mutex) 和标准布局类型 (StandardLayoutType) 的所有要求。

成员类型
	native_handle_type(可选)	实现定义
成员函数
	(构造函数)	构造互斥(公开成员函数)
	(析构函数)	销毁互斥(公开成员函数)
	operator=[被删除] 	不可复制赋值(公开成员函数)
锁定
	lock	锁定互斥,若互斥不可用则阻塞(公开成员函数)
	try_lock	尝试锁定互斥,若互斥不可用则返回(公开成员函数)
	unlock	解锁互斥(公开成员函数)
原生句柄
	native_handle	返回底层实现定义的原生句柄(公开成员函数)
//recursive_mutex 的使用场景之一是保护类中的共享状态,而类的成员函数可能相互调用

运行此代码
#include <iostream>
#include <thread>
#include <mutex>
 
class X {
    std::recursive_mutex m;
    std::string shared;
  public:
    void fun1() {
      std::lock_guard<std::recursive_mutex> lk(m);
      shared = "fun1";
      std::cout << "in fun1, shared variable is now " << shared << '\n';
    }
    void fun2() {
      std::lock_guard<std::recursive_mutex> lk(m);
      shared = "fun2";
      std::cout << "in fun2, shared variable is now " << shared << '\n';
      fun1(); // 递归锁在此处变得有用
      std::cout << "back in fun2, shared variable is " << shared << '\n';
    };
};
 
int main() 
{
    X x;
    std::thread t1(&X::fun1, &x);
    std::thread t2(&X::fun2, &x);
    t1.join();
    t2.join();
}

可能的输出:

in fun1, shared variable is now fun1
in fun2, shared variable is now fun2
in fun1, shared variable is now fun1
back in fun2, shared variable is fun1

shared_mutex

shared_mutex 类是一个同步原语,可用于保护共享数据不被多个线程同时访问。与便于独占访问的其他互斥类型不同,shared_mutex 拥有二个访问级别:

  • 共享 - 多个线程能共享同一互斥的所有权。
  • 独占性 - 仅一个线程能占有互斥。
  • 若一个线程已获取独占性锁(通过 lock 、 try_lock ),则无其他线程能获取该锁(包括共享的)。

仅当任何线程均未获取独占性锁时,共享锁能被多个线程获取(通过 lock_shared 、 try_lock_shared )。
在一个线程内,同一时刻只能获取一个锁(共享或独占性)。
共享互斥体在能由任何数量的线程同时读共享数据,但一个线程只能在无其他线程同时读写时写同一数据时特别有用。
shared_mutex 类满足共享互斥体 (SharedMutex) 和标准布局类型 (StandardLayoutType) 的所有要求。

成员类型
	native_handle_type(可选)	实现定义
成员函数
	(构造函数)	构造互斥(公开成员函数)
	(析构函数)	销毁互斥(公开成员函数)
	operator=[被删除]	不可复制赋值(公开成员函数)
排他性锁定
	lock	锁定互斥,若互斥不可用则阻塞(公开成员函数)
	try_lock	尝试锁定互斥,若互斥不可用则返回(公开成员函数)
	unlock	解锁互斥(公开成员函数)
共享锁定
	lock_shared	为共享所有权锁定互斥,若互斥不可用则阻塞(公开成员函数)
	try_lock_shared	尝试为共享所有权锁定互斥,若互斥不可用则返回(公开成员函数)
	unlock_shared	解锁互斥(共享所有权)(公开成员函数)
原生句柄
	native_handle	返回底层实现定义的原生句柄(公开成员函数)
#include <iostream>
#include <mutex>  // 对于 std::unique_lock
#include <shared_mutex>
#include <thread>
 
class ThreadSafeCounter {
 public:
  ThreadSafeCounter() = default;
 
  // 多个线程/读者能同时读计数器的值。
  unsigned int get() const {
    std::shared_lock<std::shared_mutex> lock(mutex_);
    return value_;
  }
 
  // 只有一个线程/写者能增加/写线程的值。
  void increment() {
    std::unique_lock<std::shared_mutex> lock(mutex_);
    value_++;
  }
 
  // 只有一个线程/写者能重置/写线程的值。
  void reset() {
    std::unique_lock<std::shared_mutex> lock(mutex_);
    value_ = 0;
  }
 
 private:
  mutable std::shared_mutex mutex_;
  unsigned int value_ = 0;
};
 
int main() {
  ThreadSafeCounter counter;
 
  auto increment_and_print = [&counter]() {
    for (int i = 0; i < 3; i++) {
      counter.increment();
      std::cout << std::this_thread::get_id() << ' ' << counter.get() << '\n';
 
      // 注意:写入 std::cout 实际上也要由另一互斥同步。省略它以保持示例简洁。
    }
  };
 
  std::thread thread1(increment_and_print);
  std::thread thread2(increment_and_print);
 
  thread1.join();
  thread2.join();
}
 
// 解释:下列输出在单核机器上生成。 thread1 开始时,它首次进入循环并调用 increment() ,
// 随后调用 get() 。然而,在它能打印返回值到 std::cout 前,调度器将 thread1 置于休眠
// 并唤醒 thread2 ,它显然有足够时间一次运行全部三个循环迭代。再回到 thread1 ,它仍在首个
// 循环迭代中,它最终打印其局部的计数器副本的值,即 1 到 std::cout ,再运行剩下二个循环。
// 多核机器上,没有线程被置于休眠,且输出更可能为递增顺序。

可能的输出:

单核:
123084176803584 2
123084176803584 3
123084176803584 4
123084185655040 1
123084185655040 5
123084185655040 6
多核:
140623314495232 2
140623314495232 3
140623314495232 4
140623306102528 4
140623306102528 5
140623306102528 6
### C++ 中 `mutex` 的使用方法和最佳实践 C++ 标准库提供了多种机制用于多线程编程中的同步操作,其中 `mutex`(互斥锁)是实现线程安全访问共享资源的核心工具之一。合理使用 `mutex` 可以有效避免数据竞争(data race)和未定义行为,提高程序的稳定性和可维护性。 #### 基本使用方法 `std::mutex` 是最常用的互斥锁类型,适用于大多数线程同步场景。它提供了 `lock()` 和 `unlock()` 方法用于手动控制锁的获取与释放。为了避免手动解锁带来的资源泄漏风险,通常推荐使用 `std::lock_guard` 或 `std::unique_lock` 来自动管理锁的生命周期。 ```cpp #include <iostream> #include <thread> #include <mutex> std::mutex mtx; int shared_data = 0; void increment_data() { std::lock_guard<std::mutex> lock(mtx); // 自动加锁 shared_data++; // 自动解锁 } int main() { std::thread t1(increment_data); std::thread t2(increment_data); t1.join(); t2.join(); std::cout << "Shared data value: " << shared_data << std::endl; return 0; } ``` `std::unique_lock` 相比 `std::lock_guard` 更加灵活,支持手动解锁、尝试锁定(`try_lock()`)、定时锁定(`try_lock_for()` 和 `try_lock_until()`)以及锁的移动操作[^4]。 #### 不同类型的互斥锁 - **`std::mutex`**:适用于大多数场景,要求同一线程不能多次锁定同一个互斥量。 - **`std::recursive_mutex`**:允许同一线程多次锁定同一个互斥量,适用于递归调用或复杂的函数调用链。 - **`std::shared_timed_mutex`**:支持多个读线程同时访问共享资源,但写线程独占访问。适用于读多写少的场景,如配置管理或缓存系统[^2]。 #### 最佳实践 1. **避免死锁**:确保多个互斥锁的加锁顺序一致,避免不同线程以不同顺序加锁导致死锁。可以使用 `std::lock` 或 `std::scoped_lock`(C++17 引入)来一次性锁定多个互斥量。 ```cpp std::mutex mtx1, mtx2; void thread_func() { std::scoped_lock lock(mtx1, mtx2); // C++17 支持 // 执行操作 } ``` 2. **使用 RAII 模式管理锁**:推荐使用 `std::lock_guard` 或 `std::unique_lock`,它们在构造时加锁、析构时自动解锁,有效防止锁未释放的问题。 3. **减少锁的粒度**:尽量减小加锁的代码块范围,避免长时间持有锁,从而提高并发性能。 4. **选择合适的互斥锁类型**:根据实际需求选择合适的互斥锁。例如,读写频繁的场景可以使用 `std::shared_timed_mutex` 来提高并发性。 5. **避免过度同步**:并非所有共享数据都需要加锁。可以使用原子操作(`std::atomic`)来处理简单的变量操作,减少锁的使用[^1]。 6. **设计模式与最佳实践结合**:现代 C++ 开发中,设计模式如“资源获取即初始化(RAII)”、“单例模式”等与锁机制结合使用,有助于编写结构清晰、可维护性强的多线程代码[^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

吃米饭

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

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

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

打赏作者

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

抵扣说明:

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

余额充值