C++ 多线程详解
- [一、C++ 多线程详解](#一、C++ 多线程详解)
-
- 1、创建线程
- 2、传递参数
- [3、 线程同步](#3、 线程同步)
-
- [(1) 互斥锁 (`std::mutex`)](#(1) 互斥锁 (
std::mutex)) - [(2) 条件变量 (`std::condition_variable`)](#(2) 条件变量 (
std::condition_variable))
- [(1) 互斥锁 (`std::mutex`)](#(1) 互斥锁 (
- 4、异步操作 (`std::async`, `std::future`)
- 5、原子操作 (`std::atomic`)
- 6、线程局部存储 (`thread_local`)
- 7、性能考量与最佳实践
- 二、代码示例

一、C++ 多线程详解
多线程允许程序同时执行多个任务,充分利用多核处理器资源,提高程序性能,尤其在处理 I/O 密集型或并行计算任务时效果显著。C++11 标准引入了 <thread> 头文件,提供了对原生线程操作的支持。
1、创建线程
最基本的操作是创建一个线程来执行函数。使用 std::thread 类。
cpp
#include <iostream>
#include <thread>
using namespace std;
void hello() {
std::cout << "Hello from thread!\n";
}
int main() {
std::thread t(hello); // 创建线程并执行 hello 函数
t.join(); // 等待线程结束
return 0;
}
std::thread t(func, args...):创建一个线程对象t,它将执行函数func,并可传递参数args...。.join():主线程阻塞,等待子线程执行完毕。必须调用join()或detach()之一。.detach():分离线程,使其独立运行,主线程不再等待。分离后不能再join。
2、传递参数
向线程函数传递参数遵循普通函数参数传递规则,但需注意参数是按值复制传递 的。若要传递引用,需使用 std::ref 或 std::cref。
cpp
#include <iostream>
#include <thread>
using namespace std;
void print_value(int& x) {
x *= 2;
std::cout << "Value in thread: " << x << '\n';
}
int main() {
int num = 5;
std::thread t(print_value, std::ref(num)); // 传递引用
t.join();
std::cout << "Value in main: " << num << '\n'; // 输出 10
return 0;
}
3、 线程同步
当多个线程访问共享数据时,可能引发数据竞争 (Data Race),导致未定义行为。需要使用同步原语保护共享数据。
(1) 互斥锁 (std::mutex)
最基本的同步机制。通过 lock() 和 unlock() 确保同一时间只有一个线程访问临界区。
cpp
#include <mutex>
#include <iostream>
#include <thread>
using namespace std;
std::mutex mtx;
int counter = 0;
void increment() {
for (int i = 0; i < 100000; ++i) {
mtx.lock(); // 加锁
++counter; // 临界区操作
mtx.unlock(); // 解锁
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "Counter: " << counter << '\n'; // 应为 200000
return 0;
}
注意 :手动 lock/unlock 容易出错(如忘记解锁)。推荐使用 std::lock_guard 或 std::unique_lock,它们在构造时加锁,析构时自动解锁(RAII 思想)。
cpp
void safe_increment() {
for (int i = 0; i < 100000; ++i) {
std::lock_guard<std::mutex> lock(mtx); // 构造时加锁,析构时解锁
++counter;
}
}
(2) 条件变量 (std::condition_variable)
用于线程间的条件等待和通知。常与互斥锁配合使用,实现线程等待特定条件成立。
cpp
#include <condition_variable>
#include <queue>
#include <iostream>
#include <thread>
using namespace std;
std::mutex mtx;
std::condition_variable cv;
std::queue<int> data_queue;
bool done = false;
void producer() {
for (int i = 0; i < 5; ++i) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::lock_guard<std::mutex> lock(mtx);
data_queue.push(i);
cv.notify_one(); // 通知一个等待线程
}
{
std::lock_guard<std::mutex> lock(mtx);
done = true;
}
cv.notify_all(); // 通知所有等待线程
}
void consumer() {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return !data_queue.empty() || done; }); // 等待条件满足
if (done && data_queue.empty()) break;
int data = data_queue.front();
data_queue.pop();
lock.unlock();
std::cout << "Consumed: " << data << '\n';
}
}
int main() {
std::thread prod(producer);
std::thread cons(consumer);
prod.join();
cons.join();
return 0;
}
.wait(lock, predicate):释放锁lock并阻塞线程,直到被notify_*唤醒且predicate条件为true。唤醒后会自动重新获得锁。.notify_one():随机唤醒一个等待线程。.notify_all():唤醒所有等待线程。
4、异步操作 (std::async, std::future)
<future> 头文件提供了更高级的异步操作接口。
std::async:异步执行函数,返回一个std::future对象。std::future:存储异步操作的结果。通过.get()获取结果(会阻塞直到结果准备好)。
cpp
#include <future>
#include <cmath>
#include <iostream>
#include <thread>
using namespace std;
double calculate_sqrt(double x) {
return std::sqrt(x);
}
int main() {
std::future<double> result = std::async(calculate_sqrt, 25.0);
// ... 主线程可以同时做其他事情 ...
std::cout << "Result: " << result.get() << '\n'; // 输出 5.0
return 0;
}
5、原子操作 (std::atomic)
对于简单的数据类型(如 int, bool),可以使用原子类型避免锁的开销。原子操作保证该操作在多线程环境下是不可分割的。
cpp
#include <atomic>
#include <iostream>
#include <thread>
using namespace std;
std::atomic<int> atomic_counter(0);
void atomic_increment() {
for (int i = 0; i < 100000; ++i) {
++atomic_counter; // 原子递增
}
}
int main() {
std::thread t1(atomic_increment);
std::thread t2(atomic_increment);
t1.join();
t2.join();
std::cout << "Atomic Counter: " << atomic_counter << '\n'; // 应为 200000
return 0;
}
6、线程局部存储 (thread_local)
使用 thread_local 关键字声明变量,使每个线程拥有该变量的独立副本。
cpp
#include <iostream>
#include <thread>
using namespace std;
thread_local int thread_specific_id = 0;
void print_id() {
std::cout << "Thread ID: " << thread_specific_id << '\n';
thread_specific_id = std::rand() % 100; // 修改本线程的副本
}
int main() {
std::thread t1(print_id); // 输出 0
std::thread t2(print_id); // 输出 0
t1.join();
t2.join();
return 0;
}
7、性能考量与最佳实践
- 避免过度同步:锁会带来开销。尽量减少临界区范围。
- 注意死锁:多个线程互相等待对方释放锁。避免嵌套锁或按固定顺序加锁。
- 警惕虚假共享:多个线程频繁访问同一缓存行的不同变量,导致缓存失效。可以通过填充字节或重新组织数据结构来避免。
- 优先使用高级抽象 :如
std::async,std::future,std::atomic等,它们通常更安全高效。 - 使用线程池:频繁创建销毁线程代价高。线程池可以复用线程。
二、代码示例
C++ 代码实现
cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <vector>
#include <chrono>
// 共享数据:缓冲区和同步工具
std::mutex mtx; // 互斥锁,保护共享缓冲区
std::condition_variable cv_producer; // 条件变量,用于生产者等待
std::condition_variable cv_consumer; // 条件变量,用于消费者等待
std::queue<int> buffer; // 缓冲区队列
const int BUFFER_SIZE = 5; // 缓冲区最大容量
const int NUM_ITEMS = 10; // 每个生产者生产的总项目数
// 生产者线程函数
void producer(int id) {
for (int i = 0; i < NUM_ITEMS; ++i) {
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟生产延迟
std::unique_lock<std::mutex> lock(mtx);
// 等待缓冲区不满(条件变量检查)
cv_producer.wait(lock, []{ return buffer.size() < BUFFER_SIZE; });
int item = i * 100 + id; // 生成数据项(例如:生产者ID和项目索引组合)
buffer.push(item);
std::cout << "Producer " << id << " produced item: " << item << " (Buffer size: " << buffer.size() << ")" << std::endl;
lock.unlock();
cv_consumer.notify_one(); // 通知一个消费者
}
}
// 消费者线程函数
void consumer(int id) {
for (int i = 0; i < NUM_ITEMS; ++i) {
std::unique_lock<std::mutex> lock(mtx);
// 等待缓冲区不空(条件变量检查)
cv_consumer.wait(lock, []{ return !buffer.empty(); });
int item = buffer.front();
buffer.pop();
std::cout << "Consumer " << id << " consumed item: " << item << " (Buffer size: " << buffer.size() << ")" << std::endl;
lock.unlock();
cv_producer.notify_one(); // 通知一个生产者
std::this_thread::sleep_for(std::chrono::milliseconds(150)); // 模拟消费延迟
}
}
int main() {
const int NUM_PRODUCERS = 2; // 生产者线程数
const int NUM_CONSUMERS = 2; // 消费者线程数
std::vector<std::thread> producers;
std::vector<std::thread> consumers;
// 创建生产者线程
for (int i = 0; i < NUM_PRODUCERS; ++i) {
producers.push_back(std::thread(producer, i));
}
// 创建消费者线程
for (int i = 0; i < NUM_CONSUMERS; ++i) {
consumers.push_back(std::thread(consumer, i));
}
// 等待所有线程完成
for (auto& t : producers) {
t.join();
}
for (auto& t : consumers) {
t.join();
}
std::cout << "All threads completed. Program exits." << std::endl;
return 0;
}
代码解释(逐步说明)
-
头文件导入:
<thread>:用于创建和管理线程(std::thread)。<mutex>:提供互斥锁(std::mutex)来保护共享数据。<condition_variable>:实现条件变量(std::condition_variable)用于线程间协调。<queue>和<vector>:用于缓冲区和线程管理。<chrono>:用于模拟线程延迟。
-
共享数据和同步工具:
std::queue<int> buffer:共享缓冲区,存储数据项。BUFFER_SIZE = 5:缓冲区容量限制,防止溢出。std::mutex mtx:互斥锁,确保对缓冲区的访问是原子的(避免数据竞争)。std::condition_variable cv_producer和cv_consumer:条件变量,分别用于生产者和消费者的等待-通知机制。
-
生产者线程函数:
- 每个生产者(通过
id标识)生产NUM_ITEMS个项目。 - 使用
std::unique_lock锁定互斥锁,并通过cv_producer.wait等待缓冲区不满的条件。 - 生产数据项后,放入缓冲区,并通过
cv_consumer.notify_one()通知一个消费者线程。
- 每个生产者(通过
-
消费者线程函数:
- 每个消费者(通过
id标识)消费NUM_ITEMS个项目。 - 使用
std::unique_lock锁定互斥锁,并通过cv_consumer.wait等待缓冲区不空的条件。 - 取出数据项后,通过
cv_producer.notify_one()通知一个生产者线程。
- 每个消费者(通过
-
主函数:
- 创建多个生产者线程(
NUM_PRODUCERS = 2)和消费者线程(NUM_CONSUMERS = 2)。 - 使用
std::thread启动线程,并通过join()等待所有线程结束。 - 输出线程完成信息。
- 创建多个生产者线程(
运行结果
cpp
Producer 0 produced item: 0 (Buffer size: 1)
Consumer 1 consumed item: 0 (Buffer size: 0)
Producer 1 produced item: 1 (Buffer size: 1)
Consumer 0 consumed item: 1 (Buffer size: 0)
Producer 0 produced item: 100 (Buffer size: 1)
Producer 1 produced item: 101 (Buffer size: 2)
Consumer 1 consumed item: 100 (Buffer size: 1)
Consumer 0 consumed item: 101 (Buffer size: 0)
Producer 0 produced item: 200 (Buffer size: 1)
Producer 1 produced item: 201 (Buffer size: 2)
Consumer 1 consumed item: 200 (Buffer size: 1)
Producer 0 produced item: 300 (Buffer size: 2)
Consumer 0 consumed item: 201 (Buffer size: 1)
Producer 1 produced item: 301 (Buffer size: 2)
Producer 0 produced item: 400 (Buffer size: 3)
Producer 1 produced item: 401 (Buffer size: 4)
Consumer 1 consumed item: 300 (Buffer size: 3)
Consumer 0 consumed item: 301 (Buffer size: 2)
Producer 0 produced item: 500 (Buffer size: 3)
Producer 1 produced item: 501 (Buffer size: 4)
Consumer 1 consumed item: 400 (Buffer size: 3)
Consumer 0 consumed item: 401 (Buffer size: 2)
Producer 0 produced item: 600 (Buffer size: 3)
Producer 1 produced item: 601 (Buffer size: 4)
Producer 0 produced item: 700 (Buffer size: 5)
Consumer 1 consumed item: 500 (Buffer size: 4)
Producer 1 produced item: 701 (Buffer size: 5)
Consumer 0 consumed item: 501 (Buffer size: 4)
Producer 0 produced item: 800 (Buffer size: 5)
Consumer 1 consumed item: 600 (Buffer size: 4)
Producer 1 produced item: 801 (Buffer size: 5)
Consumer 0 consumed item: 601 (Buffer size: 4)
Producer 0 produced item: 900 (Buffer size: 5)
Consumer 1 consumed item: 700 (Buffer size: 4)
Consumer 0 consumed item: 701 (Buffer size: 3)
Producer 1 produced item: 901 (Buffer size: 4)
Consumer 1 consumed item: 800 (Buffer size: 3)
Consumer 0 consumed item: 801 (Buffer size: 2)
Consumer 0 consumed item: 900 (Buffer size: 1)
Consumer 1 consumed item: 901 (Buffer size: 0)
All threads completed. Program exits.
C:\Users\徐鹏\Desktop\新建文件夹\Project1\x64\Debug\Project1.exe (进程 35384)已退出,代码为 0 (0x0)。
要在调试停止时自动关闭控制台,请启用"工具"->"选项"->"调试"->"调试停止时自动关闭控制台"。
按任意键关闭此窗口. . .
