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

线程锁-互斥、自旋、读写、原子操作、线程池

1.自旋锁

2.互斥锁

原子操作

线程池

多线程同步编程详解

多线程同步是并发编程中的核心概念,下面我将详细介绍互斥锁、自旋锁、读写锁、原子操作和线程池,并提供代码示例、使用条件和常见易错点。

1. 互斥锁 (Mutex)

基本概念

互斥锁是最常用的线程同步机制,用于保护共享资源,确保同一时间只有一个线程可以访问临界区。

代码示例 (C++11)

#include <iostream>
#include <thread>
#include <mutex>std::mutex mtx;
int shared_data = 0;void increment() {for (int i = 0; i < 100000; ++i) {mtx.lock();++shared_data;mtx.unlock();}
}int main() {std::thread t1(increment);std::thread t2(increment);t1.join();t2.join();std::cout << "Final value: " << shared_data << std::endl;return 0;
}

使用条件

  • 当多个线程需要访问同一共享资源时
  • 临界区代码执行时间较长(如文件I/O、复杂计算)

注意事项

  1. 避免死锁​:确保锁总是被释放,可以使用std::lock_guardstd::unique_lock自动管理
  2. 锁粒度​:锁的范围不宜过大,否则会降低并发性能
  3. 异常安全​:临界区内可能抛出异常,导致锁无法释放

易错点

  • 忘记解锁(推荐使用RAII风格的锁管理)
  • 锁的嵌套使用不当导致死锁
  • 在不同函数中加锁/解锁,导致逻辑混乱

2. 自旋锁 (Spinlock)

基本概念

自旋锁是一种忙等待锁,线程在获取锁失败时会循环检查锁状态,而不是被挂起。

代码示例 (C++11)

#include <atomic>
#include <thread>class Spinlock {std::atomic_flag flag = ATOMIC_FLAG_INIT;
public:void lock() {while (flag.test_and_set(std::memory_order_acquire));}void unlock() {flag.clear(std::memory_order_release);}
};Spinlock spin;
int shared_data = 0;void increment() {for (int i = 0; i < 100000; ++i) {spin.lock();++shared_data;spin.unlock();}
}int main() {std::thread t1(increment);std::thread t2(increment);t1.join();t2.join();std::cout << "Final value: " << shared_data << std::endl;return 0;
}

使用条件

  • 临界区代码执行时间非常短
  • 不希望线程被挂起(避免上下文切换开销)
  • 多核处理器环境

注意事项

  1. CPU占用​:自旋锁会持续占用CPU
  2. 单核慎用​:单核CPU上可能导致性能问题
  3. 优先级反转​:高优先级线程可能被低优先级线程阻塞

易错点

  • 在长时间临界区使用自旋锁(应改用互斥锁)
  • 忘记释放自旋锁
  • 在单核系统上使用导致性能下降

3. 读写锁 (Read-Write Lock)

基本概念

读写锁允许多个读操作同时进行,但写操作需要独占访问,适用于读多写少的场景。

代码示例 (C++17)

#include <shared_mutex>
#include <thread>
#include <vector>std::shared_mutex rw_mutex;
int shared_data = 0;void reader(int id) {for (int i = 0; i < 5; ++i) {{std::shared_lock lock(rw_mutex);std::cout << "Reader " << id << " sees: " << shared_data << std::endl;}std::this_thread::sleep_for(std::chrono::milliseconds(100));}
}void writer(int id) {for (int i = 0; i < 2; ++i) {{std::unique_lock lock(rw_mutex);++shared_data;std::cout << "Writer " << id << " updated to: " << shared_data << std::endl;}std::this_thread::sleep_for(std::chrono::milliseconds(200));}
}int main() {std::vector<std::thread> readers;for (int i = 0; i < 5; ++i) {readers.emplace_back(reader, i);}std::vector<std::thread> writers;for (int i = 0; i < 2; ++i) {writers.emplace_back(writer, i);}for (auto& t : readers) t.join();for (auto& t : writers) t.join();return 0;
}

使用条件

  • 读操作远多于写操作
  • 读操作不需要修改共享数据
  • 读操作持续时间较长

注意事项

  1. 写者饥饿​:持续有读者可能导致写者无法获取锁
  2. 升级问题​:从读锁升级到写锁通常不支持
  3. 公平性​:某些实现可能不公平,导致某些线程长期得不到机会

易错点

  • 在持有读锁时修改数据(未定义行为)
  • 错误估计读写比例(写多读少时性能可能不如互斥锁)
  • 忽略锁的升级限制

4. 原子操作 (Atomic Operations)

基本概念

原子操作是不可分割的操作,由CPU直接保证其原子性,无需额外锁机制。

代码示例 (C++11)

#include <atomic>
#include <thread>
#include <vector>std::atomic<int> counter(0);void increment(int n) {for (int i = 0; i < n; ++i) {counter.fetch_add(1, std::memory_order_relaxed);}
}int main() {const int num_threads = 10;const int increments_per_thread = 100000;std::vector<std::thread> threads;for (int i = 0; i < num_threads; ++i) {threads.emplace_back(increment, increments_per_thread);}for (auto& t : threads) t.join();std::cout << "Final counter value: " << counter << std::endl;return 0;
}

使用条件

  • 简单的共享变量操作(如计数器、标志位)
  • 需要最高性能的同步场景
  • 对单个变量的操作

注意事项

  1. 内存顺序​:理解并正确使用memory_order
  2. 复合操作​:多个原子操作组合不是原子的
  3. ABA问题​:某些场景下需要考虑

易错点

  • 错误使用memory_order导致可见性问题
  • 认为多个原子操作组合是原子的
  • 忽略缓存一致性问题

5. 线程池 (Thread Pool)

基本概念

线程池预先创建一组线程,避免频繁创建销毁线程的开销,提高任务执行效率。

代码示例 (C++11简单实现)

#include <vector>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <future>class ThreadPool {
public:ThreadPool(size_t threads) : stop(false) {for (size_t i = 0; i < threads; ++i) {workers.emplace_back([this] {while (true) {std::function<void()> task;{std::unique_lock<std::mutex> lock(this->queue_mutex);this->condition.wait(lock, [this] {return this->stop || !this->tasks.empty();});if (this->stop && this->tasks.empty()) return;task = std::move(this->tasks.front());this->tasks.pop();}task();}});}}template<class F, class... Args>auto enqueue(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type> {using return_type = typename std::result_of<F(Args...)>::type;auto task = std::make_shared<std::packaged_task<return_type()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));std::future<return_type> res = task->get_future();{std::unique_lock<std::mutex> lock(queue_mutex);if(stop) throw std::runtime_error("enqueue on stopped ThreadPool");tasks.emplace([task](){ (*task)(); });}condition.notify_one();return res;}~ThreadPool() {{std::unique_lock<std::mutex> lock(queue_mutex);stop = true;}condition.notify_all();for (std::thread &worker : workers)worker.join();}private:std::vector<std::thread> workers;std::queue<std::function<void()>> tasks;std::mutex queue_mutex;std::condition_variable condition;bool stop;
};// 使用示例
int main() {ThreadPool pool(4);std::vector<std::future<int>> results;for (int i = 0; i < 8; ++i) {results.emplace_back(pool.enqueue([i] {std::cout << "Task " << i << " started\n";std::this_thread::sleep_for(std::chrono::seconds(1));std::cout << "Task " << i << " finished\n";return i*i;}));}for (auto&& result : results)std::cout << "Result: " << result.get() << std::endl;return 0;
}

使用条件

  • 需要执行大量短期任务
  • 避免频繁创建销毁线程的开销
  • 需要限制并发线程数量

注意事项

  1. 任务队列大小​:无界队列可能导致内存耗尽
  2. 异常处理​:任务中的异常需要妥善处理
  3. 线程数量​:根据CPU核心数和任务类型选择合适数量
  4. 任务依赖​:注意任务间的依赖关系

易错点

  • 忘记正确处理线程池关闭
  • 任务抛异常导致线程退出
  • 任务之间有共享状态但未同步
  • 线程数量设置不合理(过多或过少)

综合对比

同步机制适用场景优点缺点
互斥锁一般共享资源访问简单可靠,线程挂起不占CPU上下文切换开销大
自旋锁极短临界区,多核无上下文切换,响应快忙等待消耗CPU
读写锁读多写少场景允许多个读者并发实现复杂,可能写者饥饿
原子操作简单变量操作最高性能,无锁只能用于简单操作
线程池大量短期任务避免线程创建开销,控制并发度实现复杂,需管理任务队列

最佳实践建议

  1. 优先考虑更高层次的抽象​:如任务并行库(TBB)、OpenMP等
  2. 避免过早优化​:先用简单互斥锁,有性能问题再考虑其他
  3. 尽量减少共享数据​:通过设计减少同步需求
  4. 使用工具检测问题​:如ThreadSanitizer检测数据竞争
  5. 理解内存模型​:特别是使用原子操作时
  6. 测试多线程代码​:多线程bug往往难以重现,需要专门测试

多线程编程复杂但功能强大,正确使用这些同步机制可以构建高效可靠的并发程序。

http://www.dtcms.com/a/312532.html

相关文章:

  • [硬件电路-147]:模拟电路 - DC/DC电压的三种架构:升压(Boost)、降压(Buck)或升降压(Buck-Boost)
  • GLM-4.5 解读:统一推理、编码与智能体的全能王
  • 利用AI渲染技术提升元宇宙用户体验的技术难点有哪些?
  • 微分方程模型:用“变化率”的语言,描绘世间万物的动态演化
  • 文本换行问题
  • [每周一更]-(第153期):**PDF终极防护指南:命令行全栈加密+一键权限锁死实战(附脚本模板)**
  • 前端JS-调用单删接口来删除多个选中文件
  • 前端 拼多多4399笔试题目
  • Spring 03 Web springMVC
  • 如何查看SoC线程的栈起始地址及大小
  • leecode2962 统计最大元素出现至少K次的子数组
  • 第12届蓝桥杯Scratch图形化【省赛】初级组 2021年4月24日
  • 从Docker衔接到导入黑马商城以及前端登录显示用户或密码错误的相关总结(个人理解,仅供参考)
  • 从传热学基础到有限元弱形式推导:拆解热传导问题Matlab有限元离散核心
  • C++ 信号处理
  • 【AI编程工具IDE/CLI/插件专栏】-国外IDE与Cursor能力对比
  • 【从零开始速通C语言1】 - 汇编语言1
  • 西门子PLC基础指令4:输出指令、立即输出指令
  • 信用衍生工具
  • 《基于特征融合的小目标检测方法及其在医学影像领域的应用研究》论文解析
  • Coin Combinations I(Dynamic Programming)
  • ThinkPHP 与 Vue.js 结合的全栈开发模式
  • 多线程(三)-线程安全原因与解决
  • Day26-二叉树的最小深度
  • 【软考中级网络工程师】知识点之 RIP 协议
  • C++ 之 【模拟实现 优先级队列】
  • SQL 地理空间原理与实现
  • slice() 和 splice()
  • 信创及一次ORACLE到OB的信创迁移
  • 自由学习记录(76)