在当今多核处理器普及的时代,多线程编程已成为提升程序性能、改善用户体验的关键技术。C++11标准库引入了原生的线程支持,标志着C++正式进入现代化并发编程时代。本章将从基础概念入手,系统性地介绍C++多线程编程的各个方面,包括线程创建与管理、同步机制、高级并发特性等。我们将对比传统的POSIX线程(pthread)与现代C++线程库,帮助开发者理解不同技术方案的优缺点,掌握在实际项目中做出正确选择的能力。
1. 线程API对比
选择合适的多线程技术栈是项目成功的关键因素之一。下表从多个维度对比了两种主流线程API的核心特性,帮助开发者根据具体需求做出技术选型决策。无论是追求跨平台一致性还是需要与现有C代码集成,理解这些差异都能确保技术决策的科学性和合理性。
| 特性 | pthread | C++11 std::thread |
|---|---|---|
| 跨平台 | 是(类Unix) | 是(C++11标准) |
| 创建线程 | pthread_create |
std::thread |
| 等待线程 | pthread_join |
join() |
| 分离线程 | pthread_detach |
detach() |
| 互斥锁 | pthread_mutex_t |
std::mutex |
| 条件变量 | pthread_cond_t |
std::condition_variable |
| 线程局部存储 | pthread_key_t |
thread_local |
| 内存模型支持 | 有限 | 完整的C++11内存模型 |
| 异常安全 | 需要手动处理 | RAII自动管理 |
| 与标准库集成 | 较差 | 完美集成 |
| 可移植性 | Unix-like系统通用 | 所有支持C++11的平台 |
| 性能开销 | 较低 | 略有封装开销 |
| 类型安全 | 弱(void*参数) | 强类型安全 |
2. pthread
POSIX线程(pthread)是Unix-like系统上传统的多线程编程接口,具有悠久的历史和广泛的生态系统支持。
其提供了底层的线程控制能力,允许开发者对线程行为进行精细控制。虽然API设计较为原始,但在系统编程、嵌入式开发和与现有C代码库集成时,pthread仍然是不可或缺的工具。
c
#include <pthread.h> // 包含 POSIX 线程(pthread)库,提供多线程相关函数
#include <stdio.h> // 包含标准输入输出库,用于 printf 等函数
#include <unistd.h> // 包含 Unix 标准库,提供 sleep 等系统调用
// 线程函数:每个线程将执行的函数
void* thread_func(void* arg) {
int id = *(int*)arg; // 将传入的 void* 参数转换为 int* 并解引用,获取线程 ID
for(int i = 0; i < 5; i++) {
printf("Thread %d: %d\n", id, i);
sleep(1); // 暂停 1 秒,模拟线程执行耗时任务
}
return NULL;
}
int main() {
pthread_t threads[3]; // 声明数组,用于存储 3 个线程的标识符
int ids[3] = {1, 2, 3}; // 线程 ID 数组,用于区分不同线程
// 创建 3 个线程
for(int i = 0; i < 3; i++) {
// &threads[i]:存储新线程标识符的地址
// thread_func:线程要执行的函数
// &ids[i]:传递给线程函数的参数(此处传递线程 ID 的地址)
pthread_create(&threads[i], NULL, thread_func, &ids[i]);
}
// 等待线程结束
for(int i = 0; i < 3; i++) {
// pthread_join:阻塞等待指定线程结束
// threads[i]:要等待的线程标识符
pthread_join(threads[i], NULL);
}
printf("All threads completed\n");
return 0;
}
2.1 互斥锁 pthread_mutex_t
本节重点讲解pthread中用于保护共享资源、防止数据竞争的核心同步原语------互斥锁(Mutex)。通过一个两个线程同时递增全局计数器的经典案例,演示了互斥锁的初始化 (pthread_mutex_init)、加锁 (pthread_mutex_lock)、解锁 (pthread_mutex_unlock) 和销毁 (pthread_mutex_destroy) 的完整流程,清晰地展示了如何确保对共享变量操作的原子性。
c
#include <stdio.h> // 包含标准输入输出库,用于 printf 等函数
#include <pthread.h> // 包含 POSIX 线程库,提供多线程和互斥锁相关功能
// 这是最常用的静态初始化方式,适用于全局或静态互斥锁
// pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t lock; // 声明一个互斥锁变量,用于线程同步
int num = 0; // 全局共享变量,将被两个线程同时访问
// 线程函数:两个线程都会执行此函数
void* thread_func(void* arg) {
pthread_mutex_lock(&lock); // 加锁
for(int i = 0; i < 10000; i++) {
num++;
}
pthread_mutex_unlock(&lock); // 解锁
return NULL;
}
int main() {
pthread_t t1, t2; // 声明两个线程标识符变量
// 初始化互斥锁
// &lock: 要初始化的互斥锁地址
pthread_mutex_init(&lock, NULL);
// 创建线程
// &t: 存储线程标识符的地址
// thread_func: 线程要执行的函数
// NULL: 传递给线程函数的参数(此处无参数)
pthread_create(&t1, NULL, thread_func, NULL);
pthread_create(&t2, NULL, thread_func, NULL);
// 等待线程结束
// t: 要等待的线程标识符
pthread_join(t1, NULL);
pthread_join(t2, NULL);
// 销毁互斥锁,释放相关资源
pthread_mutex_destroy(&lock);
printf("最终结果: %d (应该是20000)\n", num);
return 0;
}
2.2 条件变量 pthread_cond_t
本节探讨了pthread中用于线程间高效通信的另一种强大机制------条件变量(Condition Variable)。通过一个"控制器-工作线程"的协作模型,详细说明了如何让线程在条件不满足时(ready == 0)休眠等待 (pthread_cond_wait),以及另一个线程如何在条件满足时发送信号 (pthread_cond_broadcast) 来唤醒等待线程。示例强调了必须与互斥锁配合使用,并使用while循环检查条件以防止虚假唤醒,这是编写健壮同步代码的基石。
c
#include <stdio.h> // 包含标准输入输出库,用于 printf 等函数
#include <pthread.h> // 包含 POSIX 线程库,提供多线程、互斥锁和条件变量
#include <unistd.h> // 包含 Unix 标准库,提供 sleep 函数
pthread_mutex_t mutex; // 声明互斥锁,用于保护共享数据 ready
pthread_cond_t cond; // 声明条件变量,用于线程间同步
int ready = 0; // 条件变量关联的标志,0表示条件不满足,1表示条件满足
// 工作线程函数:等待条件满足后执行任务
void* worker(void* arg) {
// 将 void* 参数转换为 int 类型作为线程ID
int id = (int)(long)arg;
// 获取互斥锁,进入临界区
pthread_mutex_lock(&mutex);
// 等待条件满足(使用 while 循环防止虚假唤醒)
while(ready == 0) {
printf("线程 %d: 等待条件...\n", id);
// pthread_cond_wait: 原子地执行以下操作:
// 1. 释放互斥锁 mutex(允许其他线程修改条件)
// 2. 阻塞当前线程,等待条件变量 cond 的信号
// 3. 收到信号后,重新获取互斥锁 mutex
// 注意:调用前必须持有 mutex
pthread_cond_wait(&cond, &mutex);
}
// 条件满足,继续执行
printf("线程 %d: 条件满足,开始执行\n", id);
// 释放互斥锁,退出临界区
pthread_mutex_unlock(&mutex);
return NULL;
}
// 控制器线程函数:负责设置条件并通知工作线程
void* controller(void* arg) {
printf("控制器: 休眠2秒后触发条件\n");
sleep(2); // 休眠2秒,模拟准备工作
// 获取互斥锁,进入临界区
pthread_mutex_lock(&mutex);
// 设置条件标志为满足
ready = 1;
printf("控制器: 设置条件,唤醒所有等待线程\n");
// 唤醒所有等待在条件变量 cond 上的线程
// pthread_cond_broadcast: 广播信号,唤醒所有等待的线程
// 注意:调用时通常持有 mutex,但不是必须的
pthread_cond_broadcast(&cond);
// 释放互斥锁,退出临界区
pthread_mutex_unlock(&mutex);
return NULL;
}
int main() {
// 声明4个线程标识符:3个工作线程,1个控制器线程
pthread_t t1, t2, t3, ctrl;
// 初始化互斥锁和条件变量
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond, NULL);
// 创建3个工作线程,1个控制线程
pthread_create(&t1, NULL, worker, (void*)1);
pthread_create(&t2, NULL, worker, (void*)2);
pthread_create(&t3, NULL, worker, (void*)3);
pthread_create(&ctrl, NULL, controller, NULL);
// 等待所有线程结束(主线程阻塞等待)
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_join(ctrl, NULL);
// 清理资源
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
pthread_cond_timedwait 超时等待示例代码:
以下展示了条件变量的带超时等待功能。工作线程在等待条件满足时设置了 3 秒的超时,而控制器线程在 5 秒后才设置条件,因此工作线程会因超时而提前退出。代码演示了如何处理等待超时,以及如何计算和设置超时时间。
c
#include <stdio.h> // 标准输入输出库,提供 printf 等函数
#include <pthread.h> // POSIX 线程库,提供多线程相关函数
#include <unistd.h> // Unix 标准库,提供 sleep 函数
#include <time.h> // 时间库,提供 clock_gettime 等时间函数
#include <errno.h> // 错误号库,定义 ETIMEDOUT 等错误码
// 静态初始化互斥锁(使用宏 PTHREAD_MUTEX_INITIALIZER)
// 静态初始化的互斥锁不需要显式调用 pthread_mutex_init
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// 静态初始化条件变量(使用宏 PTHREAD_COND_INITIALIZER)
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
// 条件变量关联的标志,0表示条件不满足,1表示条件满足
int ready = 0;
// 工作线程函数:等待条件满足后执行任务
void* worker(void* arg) {
// 获取互斥锁,进入临界区
// 在调用 pthread_cond_timedwait 前必须持有互斥锁
pthread_mutex_lock(&mutex);
// 设置超时时间结构体
// timespec 结构体包含两个成员:
// tv_sec: 秒
// tv_nsec: 纳秒
struct timespec ts;
// 获取当前系统时间(使用 CLOCK_REALTIME 实时时钟)
// CLOCK_REALTIME: 系统实时时间,从 1970-01-01 00:00:00 开始
clock_gettime(CLOCK_REALTIME, &ts);
// 设置超时时间为当前时间 + 3 秒
ts.tv_sec += 3; // 将秒数增加 3
printf("线程开始等待,最多等3秒...\n");
// 使用 while 循环等待条件,防止虚假唤醒(ready == 0 表示条件不满足,需要继续等待)
while(ready == 0) {
// pthread_cond_timedwait: 带超时的条件变量等待函数
// 参数说明:
// 1. &cond: 要等待的条件变量
// 2. &mutex: 关联的互斥锁,函数会原子地释放锁并等待
// 3. &ts: 超时的绝对时间
// 函数行为:
// 1. 原子地:释放 mutex 锁,并将线程挂起到条件变量 cond 的等待队列
// 2. 等待被唤醒或超时
// 3. 被唤醒或超时后,重新获取 mutex 锁
int ret = pthread_cond_timedwait(&cond, &mutex, &ts);
// 检查函数返回值
if(ret == ETIMEDOUT) { // ETIMEDOUT: 超时错误码,定义在 errno.h
printf("等待超时!条件仍未满足,线程将退出\n");
break; // 跳出 while 循环,结束等待
} else if(ret == 0) { // 返回 0 表示成功收到信号
printf("条件满足,继续执行\n");
// 注意:被唤醒后需要重新检查 while 条件
// 如果 ready 仍为 0,可能是虚假唤醒,会继续等待
} else { // 其他错误
printf("pthread_cond_timedwait 错误: %d\n", ret);
break; // 发生错误,跳出循环
}
} // while 循环结束
// 条件满足,继续执行
printf("线程 %d: 条件满足,开始执行\n", id);
// 释放互斥锁,退出临界区
pthread_mutex_unlock(&mutex);
// 线程函数返回 NULL
return NULL;
}
// 控制器线程函数:负责设置条件并发送信号
void* controller(void* arg) {
// 休眠 5 秒,模拟耗时操作
// 注意:这里休眠 5 秒,超过工作线程的 3 秒超时时间,因此工作线程会在控制器设置条件前就超时退出
sleep(5);
// 获取互斥锁,准备修改共享变量 ready
pthread_mutex_lock(&mutex);
// 设置条件标志为满足
ready = 1;
printf("控制器: 设置条件,但工作线程可能已超时退出\n");
// 发送信号,唤醒一个等待在条件变量 cond 上的线程
// pthread_cond_signal: 唤醒至少一个等待线程
// 注意:这里工作线程可能已经超时退出,所以可能没有线程在等待
pthread_cond_signal(&cond);
// 释放互斥锁
pthread_mutex_unlock(&mutex);
return NULL;
}
int main() {
// 声明线程标识符
pthread_t t1; // 工作线程
pthread_t ctrl; // 控制器线程
// 打印程序说明
printf("=== 带超时的条件变量示例 ===\n");
printf("工作线程最多等待3秒,控制器线程5秒后才会发送信号\n");
printf("因此工作线程会因超时而退出\n\n");
// 创建线程
// 1. &t1: 存储线程标识符的地址
// 2. NULL: 线程属性,使用默认值
// 3. worker: 线程函数指针
// 4. NULL: 传递给线程函数的参数
pthread_create(&t1, NULL, worker, NULL);
pthread_create(&ctrl, NULL, controller, NULL);
// 等待线程结束
pthread_join(t1, NULL);
pthread_join(t2, NULL);
// 打印程序结束信息
printf("\n程序结束\n");
// 返回 0 表示程序正常退出
return 0;
}
3. C++11标准线程库
C++11标准线程库(std::thread)代表了现代C++并发编程的最佳实践,其将线程支持深度集成到语言和标准库中。与pthread相比,std::thread提供了更强的类型安全、更好的异常安全保证以及更优雅的API设计。
c
#include <iostream> // 包含C++标准输入输出流库,提供cout、cin等
#include <thread> // 包含C++11线程库,提供std::thread类和相关功能
#include <chrono> // 包含C++时间库,提供时间相关的函数和类型
// 普通函数:将被用作线程函数
void thread_func(int start, int end) {
// 循环从start到end(包含end)
for(int i = start; i <= end; i++) {
// 输出当前数字
std::cout << "Number: " << i << std::endl;
// 使当前线程休眠100毫秒
// std::this_thread::sleep_for: 当前线程休眠指定时长
// std::chrono::milliseconds(100): 创建100毫秒的时间间隔
// 这模拟了耗时操作,使得线程执行可见
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
// 类对象:重载了()操作符,可像函数一样被调用
class Worker {
public:
// 重载函数调用操作符
// const: 表示这个成员函数不会修改对象状态
// id: 线程标识符参数
void operator()(int id) const {
// 输出工作线程的信息
std::cout << "Worker " << id << " processing\n";
}
};
int main() {
// 使用普通函数创建线程
std::thread t1(thread_func, 1, 5);
// 使用函数对象创建线程
std::thread t2(Worker(), 42);
// 等待线程执行完毕
t1.join();
t2.join();
return 0;
}
3.1 互斥锁 Mutex
本节介绍了C++11提供的互斥量机制,重点展示了RAII(资源获取即初始化)风格的管理器类std::lock_guard。在示例中,多个线程通过lock_guard自动管理std::mutex的加锁与解锁,安全地对共享计数器进行递增操作。这种"作用域锁"模式极大地简化了资源管理,确保了即使在发生异常时锁也能被正确释放,是编写异常安全(Exception-Safe)并发代码的首选方式。
c
#include <mutex> // 包含C++互斥量(mutex)头文件,提供std::mutex等类
#include <thread> // 包含C++线程头文件,提供std::thread等
#include <vector> // 包含C++向量容器头文件,用于存储多个线程对象
#include <iostream> // 包含C++标准输入输出流库,提供cout、cin等
std::mutex g_mutex; // 全局互斥量
int shared_counter = 0; // 共享计数器,将被多个线程同时访问
// 线程函数:每个线程会增加计数器1000次
// 参数id:线程标识符,用于输出调试信息
void increment_counter(int id) {
// 每个线程执行1000次增加操作
for(int i = 0; i < 1000; i++) {
// 创建std::lock_guard对象,离开作用域时自动析构解锁
// lock是变量名,g_mutex是要锁定的互斥量
std::lock_guard<std::mutex> lock(g_mutex);
// 或者使用 unique_lock(更灵活):
// 1. 可以手动lock()/unlock()
// 2. 可以延迟加锁
// 3. 可以转移所有权
// 4. 可以与条件变量配合使用
// std::unique_lock<std::mutex> lock(g_mutex);
// 如果没有互斥量保护,多个线程可能同时读取相同的值
++shared_counter;
// 输出当前线程ID和计数器值
std::cout << "Thread " << id << ": " << shared_counter << std::endl;
}
}
int main() {
// 创建线程向量,用于管理多个线程对象
std::vector<std::thread> threads;
// 创建5个线程
for(int i = 0; i < 5; i++) {
// 使用emplace_back原地构造线程对象,避免拷贝
// emplace_back会调用std::thread的构造函数:thread(increment_counter, i)
threads.emplace_back(increment_counter, i);
}
// 等待所有线程完成
for(auto& t : threads) {
t.join();
}
// 输出最终结果
std::cout << "Final counter: " << shared_counter << std::endl;
return 0;
}
3.2 条件变量 condition_variable
条件变量实现了线程间的协调通信,允许线程在某些条件不满足时主动等待,避免忙等待带来的CPU浪费。这种机制是构建高效生产者-消费者模式、线程池等高级并发结构的基础。正确使用条件变量需要理解虚假唤醒、谓词检查和通知策略等概念,这些都将直接影响程序的正确性和性能表现。
c
#include <condition_variable> // 包含C++条件变量头文件,提供std::condition_variable
#include <mutex> // 包含C++互斥量头文件,提供std::mutex
#include <queue> // 包含C++队列容器头文件,提供std::queue
#include <thread> // 包含C++线程头文件,提供std::thread
#include <iostream> // 包含C++标准输入输出流库,提供cout、cin等
#include <chrono> // 包含C++时间库,提供时间相关的函数和类型
// 全局互斥量,保护共享数据:data_queue和finished
std::mutex mutex;
// 全局条件变量,用于线程间同步
// 当队列中有数据或生产结束时通知消费者
std::condition_variable cond;
// 共享数据队列,生产者和消费者之间的缓冲区
std::queue<int> data_queue;
// 生产结束标志
// true表示生产者已结束生产,消费者在队列空后应退出
bool finished = false;
// 生产者线程函数
void producer() {
// 生产10个数据
for(int i = 0; i < 10; i++) {
// 模拟生产过程耗时
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// 创建lock_guard对象时构造,离开作用域时自动析构解锁
std::lock_guard<std::mutex> lock(mutex);
// 将数据放入队列
data_queue.push(i);
// 输出生产信息
std::cout << "Produced: " << i << std::endl;
// 通知一个等待的消费者
// notify_one: 唤醒一个等待在cond上的线程
// 如果有多个消费者在等待,只会唤醒其中一个
cond.notify_one();
}
// 设置生产结束标志,使用单独的作用域,让锁尽快释放
{
// 获取互斥锁保护finished变量
std::lock_guard<std::mutex> lock(mutex);
// 设置结束标志
finished = true;
} // lock_guard离开作用域,自动解锁
// 通知所有消费者
// 因为生产已结束,需要唤醒所有消费者线程,让它们检查退出条件
cond.notify_all();
}
// 消费者线程函数
// 参数id: 消费者标识符
void consumer(int id) {
// 无限循环,直到满足退出条件
while(true) {
// unique_lock比lock_guard更灵活,支持手动解锁
std::unique_lock<std::mutex> lock(mutex);
// 等待条件满足cond.wait(lock, predicate):
// 1. 原子地:解锁lock,将线程挂起到条件变量的等待队列
// 2. 等待被notify_one()或notify_all()唤醒
// 3. 被唤醒后,重新获取锁
// 4. 检查predicate(谓词)是否返回true
// 5. 如果predicate返回false,则继续等待
cond.wait(lock, []{
// 等待条件:队列不为空 或 生产已结束
// 如果队列不为空,消费者可以取数据
// 如果生产已结束,消费者需要检查是否可以退出
return !data_queue.empty() || finished;
});
// 如果生产已结束且队列为空,消费者可以退出
if(finished && data_queue.empty()) {
break; // 跳出while循环,线程函数结束
}
// 从队列中取出数据
int value = data_queue.front(); // 获取队首元素(不删除)
data_queue.pop(); // 删除队首元素
// 提前手动解锁:处理数据不需要持有锁,释放锁让其他线程可以访问队列
lock.unlock();
// 模拟数据处理耗时
std::cout << "Consumer " << id << " got: " << value << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(200));
// lock会在作用域结束时自动析构,但由于已经手动unlock,析构时不会重复解锁
} // while循环结束,线程函数返回
}
int main() {
// 创建生产者线程
std::thread p(producer);
// 创建两个消费者线程
std::thread c1(consumer, 1);
std::thread c2(consumer, 2);
// 等待所有线程结束
p.join(); // 等待生产者线程结束
c1.join(); // 等待消费者1结束
c2.join(); // 等待消费者2结束
// 程序正常退出
return 0;
}
3.3 原子操作
原子操作是现代并发编程的基石,它通过在硬件层面保证操作的不可分割性,实现了无需锁的线程安全。C++11提供了完整的原子类型系统,支持多种内存顺序模型,允许开发者在性能和正确性之间做出精细权衡。理解原子操作不仅有助于编写高效的无锁数据结构,也是深入理解现代CPU内存模型的前提。
c
#include <atomic>
#include <thread>
#include <vector>
std::atomic<int> atomic_counter(0);
// 等价于:std::atomic_int atomic_counter(0);
void atomic_increment(int id) {
for(int i = 0; i < 1000; i++) {
// 原子操作,无需锁
int old_value = atomic_counter.fetch_add(1);
// 或者:atomic_counter++;
std::cout << "Thread " << id << ": " << old_value + 1 << std::endl;
}
}
// 自旋锁实现
class SpinLock {
private:
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);
}
};
int main() {
std::vector<std::thread> threads;
for(int i = 0; i < 5; i++) {
threads.emplace_back(atomic_increment, i);
}
for(auto& t : threads) {
t.join();
}
std::cout << "Final atomic counter: " << atomic_counter << std::endl;
return 0;
}
3.4 线程本地存储
线程本地存储(TLS)允许每个线程拥有变量的独立副本,是解决某些类型数据竞争问题的优雅方案。通过将特定于线程的状态与线程生命周期绑定,TLS可以避免复杂的同步逻辑,提高程序性能。C++11的thread_local关键字将这一功能直接集成到语言中,使得线程本地变量的使用变得简单直观。本节将探讨TLS的适用场景、实现原理以及性能考虑。
c
#include <thread>
#include <iostream>
// 线程局部变量
thread_local int thread_specific_value = 0;
void thread_func(int id) {
thread_specific_value = id * 100; // 每个线程有自己的副本
for(int i = 0; i < 3; i++) {
thread_specific_value += i;
std::cout << "Thread " << id << ": value = "
<< thread_specific_value << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
int main() {
std::thread t1(thread_func, 1);
std::thread t2(thread_func, 2);
std::thread t3(thread_func, 3);
t1.join();
t2.join();
t3.join();
return 0;
}
4. 异步任务
异步编程模型允许在等待操作完成的同时继续执行其他工作,是现代高并发系统的核心模式。C++11的异步任务框架(std::async, std::future, std::promise)提供了强大的工具来管理和协调异步操作。这些抽象不仅简化了并发代码的编写,还通过类型安全和异常传播机制提高了程序的可靠性。本节将深入探讨如何利用异步任务构建响应式、高效的应用程序。
c
#include <future>
#include <iostream>
#include <chrono>
// 异步任务
int compute_factorial(int n) {
int result = 1;
for(int i = 2; i <= n; i++) {
result *= i;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
return result;
}
int main() {
// 异步执行
std::future<int> future_result = std::async(std::launch::async,
compute_factorial, 10);
// 执行其他工作
std::cout << "Main thread doing other work...\n";
std::this_thread::sleep_for(std::chrono::milliseconds(50));
// 获取结果(如果未完成会阻塞)
try {
int result = future_result.get(); // 只能调用一次
std::cout << "Factorial result: " << result << std::endl;
} catch(const std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
// 使用 packaged_task
std::packaged_task<int(int)> task(compute_factorial);
std::future<int> fut = task.get_future();
std::thread task_thread(std::move(task), 8);
task_thread.detach(); // 或 join()
std::cout << "Waiting for packaged_task result...\n";
std::cout << "Result: " << fut.get() << std::endl;
return 0;
}
5. 线程池实现示例
线程池是管理线程生命周期的经典模式,它通过重用已创建的线程来避免频繁创建和销毁线程的开销。在需要处理大量短期任务的场景中,线程池能显著提高系统吞吐量和响应性。本节将实现一个完整的、生产可用的线程池,展示如何结合之前介绍的同步机制、异步任务和异常安全编程,构建健壮的并发基础设施。理解线程池的实现细节有助于开发者根据特定需求定制自己的并发框架。
c
#include <vector>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <future>
class ThreadPool {
private:
std::vector<std::thread> workers;
std::queue<std::function<void()>> tasks;
std::mutex queue_mutex;
std::condition_variable condition;
bool stop = false;
public:
ThreadPool(size_t threads) {
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();
}
}
};
// 使用示例
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;
}
6. 线程安全设计模式
设计模式是解决常见软件设计问题的可重用方案,在并发编程中尤其重要。线程安全的设计模式通过预定义的协作方式和同步策略,帮助开发者构建正确、高效的并发系统。本节将以单例模式为例,展示如何在多线程环境下实现线程安全的对象创建和访问。理解这些模式背后的原理有助于开发者在面对复杂并发场景时做出明智的设计决策。
单例模式(线程安全)
c
class Singleton {
private:
Singleton() = default;
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
static Singleton& getInstance() {
static Singleton instance; // C++11保证线程安全
return instance;
}
void doSomething() {
// 线程安全的方法
}
};