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

C++ 多线程编程

1. 核心头文件

要使用 C++ 多线程,首先需要包含以下头文件:

cpp

#include <thread>   // 线程管理(创建、休眠、获取ID等)
#include <mutex>    // 互斥锁(std::mutex, std::lock_guard, std::unique_lock)
#include <atomic>   // 原子操作(无锁编程)
#include <future>   // 异步操作(std::async, std::promise, std::future)
#include <condition_variable> // 条件变量(线程间同步)

2. 创建线程

创建线程最基本的方式是使用 std::thread 类,其构造函数接受一个可调用对象(函数、Lambda表达式、函数对象等)以及该对象所需的参数。

示例1:使用函数

cpp

#include <iostream>
#include <thread>void helloFunction() {std::cout << "Hello from function! Thread ID: " << std::this_thread::get_id() << std::endl;
}int main() {// 创建线程对象 t1,并立即执行 helloFunctionstd::thread t1(helloFunction);// 使用 Lambda 表达式创建线程 t2std::thread t2([](){std::cout << "Hello from lambda! Thread ID: " << std::this_thread::get_id() << std::endl;});// 等待两个线程执行完毕t1.join(); // main 线程阻塞,直到 t1 完成t2.join(); // main 线程阻塞,直到 t2 完成std::cout << "Main thread done." << std::endl;return 0;
}

重要提示

  • join(): 等待子线程结束,然后继续执行主线程。必须在你创建的 std::thread 对象被销毁前调用 join() 或 detach(),否则程序会调用 std::terminate 终止。

  • detach(): 将子线程与主线程分离,使其成为守护线程(daemon thread),在后台独立运行。一旦分离,你将无法再与之交互。通常用于长时间运行的任务。


3. 数据共享与竞态条件

当多个线程读写同一块共享数据时,如果没有任何同步措施,程序的执行结果将变得不确定,这就是竞态条件

示例:一个有问题的计数器

cpp

#include <thread>
#include <iostream>int counter = 0;void incrementCounter(int numIterations) {for (int i = 0; i < numIterations; ++i) {++counter; // 这不是原子操作!}
}int main() {const int numIterations = 1000000;std::thread t1(incrementCounter, numIterations);std::thread t2(incrementCounter, numIterations);t1.join();t2.join();// 结果几乎肯定不是 2000000std::cout << "Final counter value: " << counter << std::endl;return 0;
}

++counter 看起来是一条语句,但底层对应读取-修改-写入三条指令,线程可能会在这三条指令之间被中断,导致数据覆盖。


4. 使用互斥锁保护共享数据

互斥锁(Mutex) 是最常用的同步原语。它保证同一时间只有一个线程能进入被保护的代码段(临界区)。

a. std::mutex 和 std::lock_guard

std::lock_guard 是一个 RAII 类,在构造时自动加锁,在析构时自动解锁,避免了手动解锁的麻烦,异常安全。

cpp

#include <thread>
#include <iostream>
#include <mutex>int counter = 0;
std::mutex counter_mutex; // 保护 counter 的互斥锁void safeIncrement(int numIterations) {for (int i = 0; i < numIterations; ++i) {std::lock_guard<std::mutex> lock(counter_mutex); // 构造时加锁++counter; // 临界区// lock 析构时自动解锁}
}int main() {const int numIterations = 1000000;std::thread t1(safeIncrement, numIterations);std::thread t2(safeIncrement, numIterations);t1.join();t2.join();// 结果总是 2000000std::cout << "Final counter value: " << counter << std::endl;return 0;
}
b. std::unique_lock

std::unique_lock 比 std::lock_guard 更灵活(但开销稍大),可以延迟加锁、手动解锁/加锁,并且是条件变量所必需的。

cpp

std::mutex mtx;
void complexFunction() {std::unique_lock<std::mutex> lock(mtx, std::defer_lock); // 延迟加锁// ... do some work that doesn't need the lock ...lock.lock(); // 现在手动加锁// ... critical section ...lock.unlock(); // 可以手动解锁// ... more non-critical work ...// 如果锁还在,析构时会自动解锁
}

5. 原子操作

对于简单的计数器,使用 std::atomic 类型是更轻量级且高效的选择。它通过硬件指令保证操作的原子性,无需锁。

cpp

#include <atomic>
#include <thread>
#include <iostream>std::atomic<int> atomic_counter(0); // 原子整数void atomicIncrement(int numIterations) {for (int i = 0; i < numIterations; ++i) {++atomic_counter; // 这是一个原子操作// atomic_counter.fetch_add(1, std::memory_order_relaxed); // 也可以这样写}
}int main() {const int numIterations = 1000000;std::thread t1(atomicIncrement, numIterations);std::thread t2(atomicIncrement, numIterations);t1.join();t2.join();// 结果总是 2000000,且性能比用互斥锁高std::cout << "Final atomic counter value: " << atomic_counter << std::endl;return 0;
}

适用场景:适用于单个变量(整数、指针、甚至自定义结构,如果满足特定条件)的简单读写、递增、比较交换(CAS)等操作。


6. 条件变量

条件变量允许线程在某个条件不满足时主动阻塞(睡眠),直到另一个线程通知条件可能已改变。它必须和互斥锁一起使用。

经典模式:生产者-消费者

cpp

#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <iostream>std::queue<int> data_queue;
std::mutex mtx;
std::condition_variable cv;void data_producer() {for (int i = 0; i < 10; ++i) {std::this_thread::sleep_for(std::chrono::milliseconds(500)); // 模拟生产耗时{std::lock_guard<std::mutex> lock(mtx);data_queue.push(i);std::cout << "Produced: " << i << std::endl;}cv.notify_one(); // 通知一个等待的消费者}
}void data_consumer() {while (true) {std::unique_lock<std::mutex> lock(mtx);// 等待条件:队列不为空。lambda 表达式是谓词,防止虚假唤醒cv.wait(lock, []{ return !data_queue.empty(); });int data = data_queue.front();data_queue.pop();lock.unlock(); // 尽早释放锁std::cout << "Consumed: " << data << std::endl;if (data == 9) break; // 简单终止条件}
}int main() {std::thread producer(data_producer);std::thread consumer(data_consumer);producer.join();consumer.join();return 0;
}
  • cv.wait(lock, predicate):会原子地解锁 lock 并阻塞当前线程。当被 notify 唤醒时,它会重新获取锁,并检查 predicate。如果 predicate 返回 false,它会继续等待。

  • 防止虚假唤醒:使用带谓词的 wait 是标准做法,因为条件变量可能因为系统原因被意外唤醒。


7. 异步操作与 Future/Promise

<future> 头文件提供了更高层次的抽象,用于获取异步任务(在其他线程上运行的任务)的结果。

a. std::async 和 std::future

最简单的启动异步任务的方式。

cpp

#include <future>
#include <iostream>int expensiveCalculation(int x) {std::this_thread::sleep_for(std::chrono::seconds(2));return x * x;
}int main() {// 启动一个异步任务// std::launch::async 保证在新线程中执行// std::launch::deferred 表示延迟计算(直到调用 get() 时才在当前线程执行)std::future<int> result_future = std::async(std::launch::async, expensiveCalculation, 10);std::cout << "Doing some other work..." << std::endl;// .get() 会阻塞,直到异步任务完成并返回结果int result = result_future.get();std::cout << "Result: " << result << std::endl; // Output: Result: 100return 0;
}
b. std::promise 和 std::future

std::promise 允许你在一个线程中设置一个值(或异常),并通过与之关联的 std::future 在另一个线程中获取它。这是一种更手动的线程间传递结果的机制。

cpp

void doWork(std::promise<int> prom) {std::this_thread::sleep_for(std::chrono::seconds(2));prom.set_value(42); // 设置结果
}int main() {std::promise<int> myPromise;std::future<int> myFuture = myPromise.get_future();std::thread worker(doWork, std::move(myPromise)); // promise 不可复制,只能移动std::cout << "Waiting for the result..." << std::endl;int result = myFuture.get(); // 阻塞并获取结果std::cout << "The answer is: " << result << std::endl;worker.join();return 0;
}

最佳实践与注意事项

  1. 优先使用 RAII:始终使用 std::lock_guard 或 std::unique_lock 来管理锁,而不是手动调用 lock() 和 unlock(),以确保异常安全。

  2. 缩小临界区:锁的粒度要细。只在绝对必要的时候持有锁,锁住后尽快释放。

  3. 避免死锁:以固定的顺序获取多个锁(例如,总是先锁 mutex A,再锁 mutex B),或者使用 std::lock(m1, m2, ...) 来一次性锁住多个互斥量而避免死锁。

  4. 考虑无锁编程:对于简单的数据,优先考虑 std::atomic

  5. 线程不宜过多:线程的创建和上下文切换有开销。线程数量通常与 CPU 核心数相匹配是较好的起点。对于 I/O 密集型任务,可以多一些。

  6. 使用高级抽象:如果可能,优先使用 std::async 和 std::future,而不是手动管理 std::thread 和同步原语,这可以减少错误。

  7. 注意线程安全:标准库的容器和函数通常不是线程安全的(除了像 std::atomic 这样的特例)。多个线程读写同一个容器必须手动加锁。

C++ 多线程编程是一个庞大而复杂的主题,这里只是冰山一角。但掌握了这些核心概念(threadmutexatomiccondition_variablefuture),你已经可以应对绝大多数多线程开发场景了。实践中,务必谨慎小心,多使用线程检查工具(如 ThreadSanitizer)来发现潜在的竞态条件和死锁。


文章转载自:

http://nljIt5Ss.shxmr.cn
http://3jnUb6QF.shxmr.cn
http://37JIreKt.shxmr.cn
http://ArMQ8upN.shxmr.cn
http://CBvkK0IN.shxmr.cn
http://2FXt244s.shxmr.cn
http://W0RZOURv.shxmr.cn
http://JTmBv1bU.shxmr.cn
http://Q5AsQ4fT.shxmr.cn
http://VucjpfIp.shxmr.cn
http://HVLhLsBh.shxmr.cn
http://ooD0KdQC.shxmr.cn
http://Sc80IlDh.shxmr.cn
http://D3u3Pl6a.shxmr.cn
http://U02rTA4a.shxmr.cn
http://HPBEsbW3.shxmr.cn
http://AgkLhPLu.shxmr.cn
http://CPHpIc81.shxmr.cn
http://ncEUZkU7.shxmr.cn
http://3r7s7eKF.shxmr.cn
http://dh4msgoS.shxmr.cn
http://nO8HiqL7.shxmr.cn
http://8rplrPjc.shxmr.cn
http://lxL9Ku3y.shxmr.cn
http://9H6cBohq.shxmr.cn
http://QvDkwDR0.shxmr.cn
http://th6EDNLs.shxmr.cn
http://exfPjGiP.shxmr.cn
http://M5Y1jH4c.shxmr.cn
http://ORiShM63.shxmr.cn
http://www.dtcms.com/a/364455.html

相关文章:

  • 【IO】进程间通信(IPC)练习
  • CAD/BIM软件产品技术深度分析文章写作计划
  • 7.4Element Plus 分页与表格组件
  • java spring cloud 企业工程管理系统源码+二次开发+定制化服务
  • 深兰科技AI问诊助手走访打浦桥街道社区卫生服务中心
  • Llama.cpp与CUDA Graph:深度学习推理框架的兼容性深度解析
  • Elasticsearch(text和keyword)区别分析
  • 怎么删除word空白页?【图文详解】删除最后一页空白页?5种删除word文档空白页方法?
  • Few-Shot Prompting 实战:用5个例子让GPT-4学会复杂任务
  • 线程与同步
  • 【Unity Shader学习笔记】(四)Shader编程
  • Java设计模式之结构型—适配器模式
  • SQLAlchemy ORM 入门教程
  • Low-Light Image Enhancement via Structure Modeling and Guidance 论文阅读
  • SQLint3 模块如何使用
  • Linux awk命令完全指南:从原理到实战,搞定文本处理难题
  • SQL(window)日志在linux 下查看
  • LangChain实战(十三):Agent Types详解与选择策略
  • 机器学习从入门到精通 - KNN与SVM实战指南:高维空间中的分类奥秘
  • Spring Boot 工程启动时自动执行任务方法
  • 图像正向扭曲反向扭曲
  • 安全测试漫谈:如何利用X-Forwarded-For头进行IP欺骗与防护
  • 停止所有dcoker容器
  • [UT]记录uvm_config_db的错误:get中的第二个参数设置为this
  • 第6章:垃圾回收分析与调优
  • 【NVIDIA B200】1.alltoall_perf 单机性能深度分析:基于 alltoall_perf 测试数据
  • 从卡顿到丝滑:3 个实战场景教你搞定代码性能优化
  • DeepSeek、GPT-5都在卷的“快慢脑”,腾讯中科院给出了更优解:还是多模态的!
  • 什么是科技成果鉴定测试?成果鉴定测试报告带给企业什么好处?
  • c语言链表:从入门到精通