C++11标准引入了原生的多线程支持,不再需要依赖操作系统提供的线程接口,开发者可以通过标准库中的thread类轻松创建和管理多线程,同时配套提供了互斥锁、条件变量等同步工具,让多线程编程更加规范和便捷。

C++多线程的基础创建方式
使用标准库的thread类创建线程非常简单,只需要包含<thread>头文件,实例化thread对象时传入线程执行的函数即可,线程会在创建后自动启动执行。
无参数线程的创建
如果线程执行的函数不需要参数,直接传入函数名即可,示例代码如下:
#include <iostream>
#include <thread>
// 线程执行的函数
void thread_task() {
std::cout << "子线程正在执行" << std::endl;
}
int main() {
// 创建线程,传入线程函数
std::thread t(thread_task);
// 等待子线程执行完成,避免主线程提前退出导致程序异常
t.join();
std::cout << "主线程执行完成" << std::endl;
return 0;
}
带参数线程的创建
如果线程函数需要参数,可以在thread构造函数中函数名之后依次传入参数,参数会以拷贝的方式传递给线程函数,示例代码如下:
#include <iostream>
#include <thread>
#include <string>
// 带参数的线程函数
void thread_task_with_param(std::string name, int count) {
for (int i = 0; i < count; ++i) {
std::cout << name << "执行第" << i + 1 << "次任务" << std::endl;
}
}
int main() {
// 创建带参数的线程,传入函数名和两个参数
std::thread t(thread_task_with_param, "子线程A", 3);
t.join();
return 0;
}
线程的分离与等待
thread对象提供了两个核心管理方法,分别是join()和detach(),用于控制主线程和子线程的执行关系。
- join():主线程会阻塞等待当前子线程执行完成,之后才会继续往下执行,适合需要获取子线程执行结果的场景。
- detach():将子线程和主线程分离,子线程会在后台独立运行,主线程不再需要等待子线程结束,分离后的线程资源会在执行完成后自动回收,需要注意的是分离后的线程不能再调用join()。
如果thread对象销毁前既没有调用join()也没有调用detach(),程序会直接终止,因此必须合理处理线程的生命周期。
多线程同步常用工具
多个线程同时操作共享资源时,很容易出现数据竞争问题,C++标准库提供了一系列同步工具来解决这个问题。
互斥锁mutex
互斥锁用于保护共享资源,同一时间只能有一个线程持有锁,其他线程需要等待锁释放后才能获取,最常用的是std::mutex,配合std::lock_guard可以自动管理锁的获取和释放,避免忘记解锁导致死锁。
下面是一个使用互斥锁保护共享计数器的示例:
#include <iostream>
#include <thread>
#include <mutex>
// 共享计数器
int counter = 0;
// 互斥锁
std::mutex mtx;
// 线程函数,每次执行计数器加1
void increment_task(int times) {
for (int i = 0; i < times; ++i) {
// 自动加锁,作用域结束自动解锁
std::lock_guard<std::mutex> lock(mtx);
++counter;
}
}
int main() {
// 创建两个子线程
std::thread t1(increment_task, 1000);
std::thread t2(increment_task, 1000);
// 等待两个子线程执行完成
t1.join();
t2.join();
// 输出最终计数器的值,理论上是2000
std::cout << "最终计数器值:" << counter << std::endl;
return 0;
}
条件变量condition_variable
条件变量用于线程之间的通知机制,一个线程可以等待某个条件满足,另一个线程在条件满足时通知等待的线程继续执行,需要和互斥锁配合使用。
下面是一个生产者消费者模型的简单示例:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
// 共享队列
std::queue<int> data_queue;
// 互斥锁
std::mutex mtx;
// 条件变量
std::condition_variable cv;
// 结束标志
bool finished = false;
// 生产者线程函数
void producer() {
for (int i = 0; i < 5; ++i) {
{
std::lock_guard<std::mutex> lock(mtx);
data_queue.push(i);
std::cout << "生产数据:" << i << std::endl;
}
// 通知消费者线程
cv.notify_one();
}
{
std::lock_guard<std::mutex> lock(mtx);
finished = true;
}
cv.notify_all();
}
// 消费者线程函数
void consumer() {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
// 等待条件:队列不为空或者生产结束
cv.wait(lock, []{ return !data_queue.empty() || finished; });
// 如果队列为空且生产结束,退出循环
if (data_queue.empty() && finished) {
break;
}
// 取出队首数据
int data = data_queue.front();
data_queue.pop();
lock.unlock();
std::cout << "消费数据:" << data << std::endl;
}
}
int main() {
std::thread t1(producer);
std::thread t2(consumer);
t1.join();
t2.join();
return 0;
}
常见问题与注意事项
- 线程函数的参数如果是引用类型,需要使用
std::ref包装,否则thread会拷贝参数,无法修改外部变量。 - 不要对同一个thread对象多次调用join()或者detach(),会导致程序异常。
- 避免使用全局变量作为共享资源,尽量将共享资源和同步工具封装到类中,管理生命周期。
- 如果线程中会抛出异常,需要确保异常不会导致锁没有释放,使用
std::lock_guard或者std::unique_lock可以有效避免这个问题。