当前位置: 首页 > news >正文

互斥锁、读写锁、ref()函数使用

目录

  • 1. 互斥锁(Mutex)
    • 1.1 基本概念
    • 1.2 使用方法
    • 1.3 代码示例
  • 2. 读写锁(Read-Write Lock)
    • 2.1 基本概念
    • 2.2 使用方法
    • 2.3 代码示例
  • 3. 适用场景对比
  • 4. 多线程中的 std::ref() 函数
    • 4.1 为什么需要 std::ref()
    • 4.2 使用规则
    • 4.3 实际示例
    • 4.4 常见错误
  • 5. 最佳实践
  • 6. 常见问题

1. 互斥锁(Mutex)

1.1 基本概念

互斥锁(Mutual Exclusion Lock)是最基本的同步机制,用于保护共享资源。它确保在同一时刻只有一个线程能够访问被保护的资源。

核心特性:

  • 独占访问:无论读还是写,同一时刻只允许一个线程持有锁
  • 阻塞等待:其他线程在锁被占用时会被阻塞,直到锁被释放
  • 简单可靠:实现简单,适合大多数场景

1.2 使用方法

C++11提供了std::mutex类,配合RAII机制的std::lock_guardstd::unique_lock使用。

基本步骤:

  1. 定义互斥锁对象
  2. 在需要保护的代码段前加锁
  3. 执行临界区代码
  4. 自动或手动解锁

1.3 代码示例

示例1:基本使用 - 保护共享计数器
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>std::mutex mtx;           // 全局互斥锁
int shared_counter = 0;   // 共享资源void increment(int id, int times) {for (int i = 0; i < times; ++i) {// 使用 lock_guard 自动管理锁的生命周期std::lock_guard<std::mutex> lock(mtx);++shared_counter;std::cout << "线程 " << id << " 将计数器增加到: " << shared_counter << std::endl;}// lock_guard 析构时自动释放锁
}int main() {const int num_threads = 5;const int increments_per_thread = 10;std::vector<std::thread> threads;// 创建多个线程for (int i = 0; i < num_threads; ++i) {threads.emplace_back(increment, i, increments_per_thread);}// 等待所有线程完成for (auto& t : threads) {t.join();}std::cout << "最终计数器值: " << shared_counter << std::endl;std::cout << "预期值: " << num_threads * increments_per_thread << std::endl;return 0;
}
示例2:使用 unique_lock 实现更灵活的锁控制
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>std::mutex mtx;void flexible_locking(int id) {// unique_lock 允许手动控制锁的获取和释放std::unique_lock<std::mutex> lock(mtx);std::cout << "线程 " << id << " 获得锁" << std::endl;// 可以手动解锁lock.unlock();// 执行一些不需要锁保护的操作std::this_thread::sleep_for(std::chrono::milliseconds(100));// 重新获取锁lock.lock();std::cout << "线程 " << id << " 重新获得锁" << std::endl;// 析构时自动释放锁(如果还持有的话)
}int main() {std::thread t1(flexible_locking, 1);std::thread t2(flexible_locking, 2);t1.join();t2.join();return 0;
}
示例3:避免死锁 - 同时锁定多个互斥锁
#include <iostream>
#include <thread>
#include <mutex>std::mutex mtx1, mtx2;void safe_transfer(int from, int to, int amount) {// 使用 std::lock 同时锁定多个互斥锁,避免死锁std::lock(mtx1, mtx2);// 将已锁定的互斥锁交给 lock_guard 管理std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);std::cout << "从账户 " << from << " 转账 " << amount << " 到账户 " << to << std::endl;
}int main() {std::thread t1(safe_transfer, 1, 2, 100);std::thread t2(safe_transfer, 2, 1, 50);t1.join();t2.join();return 0;
}

2. 读写锁(Read-Write Lock)

2.1 基本概念

读写锁是一种更精细的锁机制,它区分读操作和写操作,允许:

  • 多个读线程同时访问:读操作不会修改数据,可以并发执行
  • 写线程独占访问:写操作需要独占锁,不能与其他读或写操作并发

核心特性:

  • 读共享:多个线程可以同时持有读锁
  • 写独占:写锁与任何其他锁互斥
  • 提高并发:在读多写少的场景下性能更优

2.2 使用方法

C++17引入了std::shared_mutex,配合std::shared_lock(读锁)和std::unique_lock(写锁)使用。

基本步骤:

  • 读操作:使用std::shared_lock获取共享锁
  • 写操作:使用std::unique_lockstd::lock_guard获取独占锁

2.3 代码示例

示例1:基本使用 - 保护共享数据结构
#include <iostream>
#include <thread>
#include <shared_mutex>
#include <vector>
#include <chrono>
#include <map>class ThreadSafeMap {
private:std::map<int, std::string> data_;mutable std::shared_mutex mutex_;  // mutable 允许在 const 方法中使用public:// 写操作:使用独占锁void insert(int key, const std::string& value) {std::unique_lock<std::shared_mutex> lock(mutex_);data_[key] = value;std::cout << "写入: [" << key << "] = " << value << std::endl;}// 读操作:使用共享锁bool find(int key, std::string& value) const {std::shared_lock<std::shared_mutex> lock(mutex_);auto it = data_.find(key);if (it != data_.end()) {value = it->second;return true;}return false;}// 读操作:获取大小size_t size() const {std::shared_lock<std::shared_mutex> lock(mutex_);return data_.size();}
};void reader(const ThreadSafeMap& map, int id) {for (int i = 0; i < 5; ++i) {std::string value;if (map.find(i, value)) {std::cout << "读线程 " << id << " 读取到: [" << i << "] = " << value << std::endl;}std::this_thread::sleep_for(std::chrono::milliseconds(10));}
}void writer(ThreadSafeMap& map, int id) {for (int i = 0; i < 3; ++i) {map.insert(i, "value_from_thread_" + std::to_string(id));std::this_thread::sleep_for(std::chrono::milliseconds(50));}
}int main() {ThreadSafeMap map;std::vector<std::thread> threads;// 创建写线程 - 注意使用 std::ref()threads.emplace_back(writer, std::ref(map), 1);threads.emplace_back(writer, std::ref(map), 2);// 创建多个读线程(可以并发执行)- 注意使用 std::cref()for (int i = 0; i < 5; ++i) {threads.emplace_back(reader, std::cref(map), i);}// 等待所有线程完成for (auto& t : threads) {t.join();}std::cout << "最终 map 大小: " << map.size() << std::endl;return 0;
}

4. 多线程中的 std::ref() 函数

4.1 为什么需要 std::ref()

在C++中创建线程时,std::thread 的构造函数会按值复制所有参数到新线程的上下文中。这意味着:

问题: 即使线程函数的参数是引用类型,直接传递变量也会被复制,而不是真正的引用传递。

void thread_func(int& value) {  // 参数是引用++value;
}int main() {int num = 0;// ❌ 错误:即使函数参数是引用,num 仍会被复制// 这会导致编译错误或未定义行为std::thread t(thread_func, num);  t.join();// num 的值不会改变!
}

解决方案: 使用 std::ref() 创建引用包装器,确保按引用传递,线程使用了&,传参的时候就要用ref()或cref()。

int main() {int num = 0;// ✅ 正确:使用 std::ref() 按引用传递std::thread t(thread_func, std::ref(num));t.join();std::cout << num << std::endl;  // 输出: 1
}

4.2 使用规则

线程函数参数类型传参方式说明
void func(int value)std::thread t(func, num);按值传递,直接传
void func(int& value)std::thread t(func, std::ref(num));非const引用,用 std::ref()
void func(const int& value)std::thread t(func, std::cref(num));const引用,用 std::cref()
void func(int* ptr)std::thread t(func, &num);指针,直接传地址

记忆口诀:

函数参数带 &,
传参就要用 ref()!

4.4 常见错误

错误1:忘记使用 std::ref()
void modify(int& value) {++value;
}int main() {int num = 0;// ❌ 编译错误!无法将右值绑定到非const引用std::thread t(modify, num);// ✅ 正确写法std::thread t(modify, std::ref(num));
}
错误2:对象生命周期问题
// ❌ 危险:引用的对象会先被销毁
std::thread create_thread() {int local_var = 0;return std::thread(func, std::ref(local_var));  // 未定义行为!
}  // local_var 在这里被销毁,但线程还在运行// ✅ 正确:确保对象生命周期足够长
class MyClass {int member_var_ = 0;std::thread thread_;public:void start() {// member_var_ 的生命周期与 MyClass 对象相同thread_ = std::thread(func, std::ref(member_var_));}~MyClass() {if (thread_.joinable()) {thread_.join();}}
};
错误3:const 引用使用 ref 而不是 cref
void read_only(const Data& data) {// 只读操作
}int main() {Data data;// ⚠️ 可以工作,但不推荐std::thread t(read_only, std::ref(data));// ✅ 推荐:const引用使用 crefstd::thread t(read_only, std::cref(data));
}

5. 最佳实践

5.1 使用RAII管理锁

推荐做法:

// ✅ 使用 lock_guard 或 unique_lock
{std::lock_guard<std::mutex> lock(mtx);// 操作共享资源
} // 自动解锁// ✅ 读写锁也使用RAII
{std::shared_lock<std::shared_mutex> lock(rwlock);  // 读锁// 读取操作
}

避免手动加解锁:

// ❌ 容易忘记解锁或异常时无法解锁
mtx.lock();
// 操作共享资源
mtx.unlock();  // 如果上面抛出异常,这行不会执行!

5.2 最小化锁的持有时间

// ✅ 只在必要时持有锁
void goodExample() {// 准备数据(不需要锁)std::string data = prepareData();{std::lock_guard<std::mutex> lock(mtx);// 只在这里持有锁shared_resource = data;}// 其他操作(不需要锁)processResult();
}// ❌ 锁持有时间过长
void badExample() {std::lock_guard<std::mutex> lock(mtx);std::string data = prepareData();  // 不需要锁保护shared_resource = data;processResult();  // 不需要锁保护
}

5.3 避免死锁

规则1:始终以相同顺序获取多个锁

// 所有线程都按 mtx1 -> mtx2 的顺序加锁
void thread1() {std::lock_guard<std::mutex> lock1(mtx1);std::lock_guard<std::mutex> lock2(mtx2);// ...
}void thread2() {std::lock_guard<std::mutex> lock1(mtx1);  // 相同顺序std::lock_guard<std::mutex> lock2(mtx2);// ...
}

规则2:使用 std::lock 同时锁定多个锁

std::lock(mtx1, mtx2);  // 原子地锁定两个锁
std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);
std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);

规则3:使用 std::scoped_lock(C++17)

// 最推荐的方式
std::scoped_lock lock(mtx1, mtx2);  // 自动处理多个锁

5.4 正确使用 std::ref()

规则:

  1. 线程函数参数是 T& 时,使用 std::ref(obj)
  2. 线程函数参数是 const T& 时,使用 std::cref(obj)
  3. 确保被引用对象的生命周期覆盖整个线程执行期
// ✅ 正确示例
void process(Data& data) { /* ... */ }
void read(const Data& data) { /* ... */ }Data data;
std::thread t1(process, std::ref(data));     // 非const引用
std::thread t2(read, std::cref(data));       // const引用

5.5 使用 const 成员函数表明读意图

class ThreadSafeContainer {
private:std::vector<int> data_;mutable std::shared_mutex mutex_;public:// const 函数表明这是读操作size_t size() const {std::shared_lock<std::shared_mutex> lock(mutex_);return data_.size();}// 非 const 函数表明这是写操作void push_back(int value) {std::unique_lock<std::shared_mutex> lock(mutex_);data_.push_back(value);}
};

6. 常见问题

Q1: std::ref() 和直接传指针有什么区别?

答:

void func_ref(int& val) { ++val; }
void func_ptr(int* val) { ++(*val); }int num = 0;// 使用引用(需要 std::ref)
std::thread t1(func_ref, std::ref(num));// 使用指针(直接传)
std::thread t2(func_ptr, &num);

区别:

  • 引用更安全(不能为空)
  • 引用语法更清晰(不需要解引用)
  • 指针更灵活(可以重新指向、可以为nullptr)
  • 现代C++推荐优先使用引用
http://www.dtcms.com/a/520731.html

相关文章:

  • 2.6、Web漏洞挖掘实战(下):XSS、文件上传与逻辑漏洞深度解析
  • 邢台做网站的公司有那个营销型网站建设营销型
  • 青岛住房和城乡建设厅网站首页iis7.0 asp网站配置
  • vue3表格显示隐藏列全屏拖动功能
  • Git Commit Message 规范:写出清晰、可维护的提交记录
  • Orleans + Kubernetes + Istio 服务网格集成深度解析
  • 51网站怎么打开注册城乡规划师有什么用
  • 相向指针|盛最多水的容器|接雨水|验证回文串
  • Web3j 中使用 Transaction 类进行以太坊交互的核心方法
  • 承德微网站开发怎么弄一个自己的网站
  • web及h5录音wav下载
  • Kotlin 协程中常见的异步返回与控制方式(速览)
  • 做网站还有前景么动漫网页设计报告
  • Maven 多配置文件的使用
  • 【双机位A卷】华为OD笔试之【哈希表】双机位A-跳房子I【Py/Java/C++/C/JS/Go六种语言】【欧弟算法】全网注释最详细分类最全的华子OD真题题解
  • SQL 拼接完全指南
  • 制作的网站wordpress还是自己写
  • 【HLS】Java实现统计HLS的m3u8清单中所有ts切片的视频持续时长
  • 免费网站建设ppt模板下载山西省建设银行网站首页
  • 增城网站建设价格郑州seo
  • 【Rust实战】从零构建高性能异步Web服务器:深入理解所有权与生命周期
  • Vlan-ACCESS接口+Trunk接口
  • 网站开发遇到的最大困难被k掉的网站怎么做才能有收录
  • SpringBoot-Web开发之文件上传
  • 5.2 类
  • 厦门协会网站建设电影网站做淘客
  • 网站建设介绍书如何注销公司流程及费用
  • 阿里国际站网站建设wordpress mysql 扩展
  • LeetCode 405 - 数字转换为十六进制数
  • 漳州做网站喊多少钱wordpress栏目更改无法显示