一、std::thread——C++11的线程

1.1基本用法

#include<iostream>
#include<thread>
using namespace std;

void work(int n){
	cout<<"子线程工作:"<< n <<endl;
}

int main(){
	thread t(work, 5);//创建线程并传参
	t.join();//等待线程执行完
	cout<<"主线程结束"<<endl;
}

👉 std::thread 构造函数的第一个参数是函数指针,后面跟参数。

1.2 join vs detach

  • join(): 阻塞主线程,等待子线程执行结束。
  • detach(): 让子线程后台执行,主线程不再等待(常用于守护线程)。
thread t1(work, 1);
t1.join(); //等待结束

thread t2(work, 2);
t2.detach();//后台运行,与t2对象分离,由系统自动回收资源

// 错误做法:既不join也不detach
std::thread t3(work, 3);
// t3销毁时会调用terminate(),导致程序异常终止

⚠注意:如果线程对象销毁时还没 join 或 detach,会抛异常,必须二选一。

二、std::atomic——原子操作

2.1问题背景

多线程同时访问一个变量时可能出现竞态条件:

#include <iostream>
#include <thread>
using namespace std;

int counter = 0;

void add(){
	for(int i = 0; i < 1000000; i++) counter++;
}

int main(){
	thread t1 (add), t2(add);
	t1.join(); t2.join();
	cout<<"counter  = "<<counter<<endl; //结果可能小于2000000, 我的运行结果是1245112
}

👉 因为 counter++ 不是原子操作。

2.2解决方案:atomic

#include <atomic>
atomic<int> counter(0);

void add() {
    for (int i = 0; i < 1000000; i++) counter++;
}

👉 atomic 保证 ++ 是原子操作,线程安全。

2.3常用方法

2.3.1 fetch_add(x): 原子加

功能:将原子变量的值加上 x,返回操作前的原始值(类似 x += n 的原子版本)。
线程安全:整个 “读取 - 修改 - 写入” 过程是原子的,不会被其他线程中断。

atomic<int> count(0);
int old_val = count.fetch_add(1);  // 等价于原子版 count++,返回操作前的值

int old_val2 = count.fetch_add(2); // 原子版 count += 2

2.3.2 fetch_sub(x): 原子减

原子减同上原子加。

2.3.3 compare_exchange_strong(expected, desired): CAS 强操作(无锁编程核心)

功能:CAS(Compare-And-Swap)是无锁编程的核心操作,逻辑如下:
1.比较原子变量的当前值与 expected(预期值)。
2.如果相等,将原子变量的值更新为 desired(目标值),返回 true。
3.如果不相等,将 expected 更新为原子变量的当前值,返回 false。
“强” 的含义:即使没有其他线程干扰,也可能因硬件原因失败(如某些架构的伪失败),但 strong 版本会持续重试直到成功或确实不匹配。

  • compare_exchange_weak
std::atomic<int> val(5);
int expected = 5;
// 尝试将 val 从 5 改为 10
bool success = val.compare_exchange_strong(expected, 10);
// 如果成功:val=10,success=true
// 如果失败:expected被更新为val的当前值,success=false

2.3.4 compare_exchange_weak(expected, desired) 弱操作

功能:与 strong 版本逻辑相同,但允许在无竞争时也可能返回失败(伪失败)。
适用场景:循环中使用(失败后可重试),性能通常比 strong 版本更高。

std::atomic<int> val(5);
int expected = 5;
// 循环确保最终成功(除非被其他线程修改)
while (!val.compare_exchange_weak(expected, 10)) {
    // 失败时,expected已被更新为当前值,无需手动重置
}

例如,用 compare_exchange_weak 实现一个线程安全的自增操作:

std::atomic<int> num(0);
void safe_increment() {
    int expected = num.load();
    while (!num.compare_exchange_weak(expected, expected + 1)) {
        // 循环重试直到成功
    }
}

👉这与直接使用 num++ 效果相同。

三、条件变量condition_variable

3.1问题背景

多个线程需要同步,例如生产者-消费者模型。

3.2基本用法

#include<iostream>
#include<thread>
#include<mutex>
#include<condition_variable>
#include<queue>
using namespace std;

queue<int> q;
mutex mtx; //互斥锁,保证对共享队列的操作是线程安全的
condition_variable cv; //条件变量,用于线程间的等待和通知机制

void producer(){
	for(int i = 1; i <= 5; i++){
		unique_lock<mutex> lock(mtx);
		q.push(i);
		cout<<"生产:"<< i <<endl;
	}
	cv.notify_one(); //唤醒一个消费者的等待线程,再通过下面的代码让出cpu
	this_thread::sleep_for(chrono::milliseconds(100));
}

void consumer(){
	for(int i = 1; i <= 5; i++){
		unique_lock<mutex> lock(mtx);
		cv.wait(lock, []{return !q.empty();}); //等待条件成立
		int val = q.front();
		q.pop();
		cout<<"消费:"<<val<<endl;
	}
}

int main(){
	thread t1(producer), t2(consumer);
	t1.join();
	t2.join();
}

四、thread_local——线程局部存储

4.1问题背景

  • 全局变量在多个线程中会被共享,有时我们需要每个线程独有的副本。
  • 这时thread_local的作用就出来了,它是线程间不共享的,互不干扰。

4.2用法

#include<iostream>
#include<thread>
using namespace std;

thread_local int counter = 0;

void work(int id){
	counter++;
	cout<<"线程"<<id<<"counter = "<<counter<<endl;
}

int main(){
	thread t1(work,1);
	thread t2(work,2);
	t1.join();
	t2.join();
}

👉 thread_local 修饰的变量在每个线程里都有独立的副本,互不干扰。

五、异常与错误处理

5.1异常处理:std::exception_ptr

5.1.1问题背景

C++11 之前,异常只能在单线程内传播。
但多线程程序中,一个线程抛出异常,可能需要在主线程里捕获。

5.1.2std::exception_ptr

C++11 提供std::exception_ptr来存储并传递异常对象。

#include<iostream>
#include<thread>
#include<exception>
using namespace std;

exception_ptr gException;

void worker(){
	try{
		throw runtime_error("子线程出错!");
	}catch(...){
		gException = current_exception();//捕获当前异常
	}
}

int main(){
	thread t(worker);
	t.join();

	if(gException){
		try{
			rethread_exception(gException);//在主线程重新抛出
		} catch(const exception& e){
			cout<<"主线程捕获异常:"<<e.what()<<endl;
		}
	}
}

👉 这样可以跨线程传递异常。

5.2 错误处理:std::error_code / std::error_category

5.2.1 问题背景

传统的errno(全局变量)不够灵活,error_code 是 C++11 引入的标准化错误处理机制。包含:错误值(value),错误信息(message),错误类别(category)。

5.2.2 error_code

#include<iostream>
#include<system_error>
using namespace std;

int main(){
	error_code ec = make_error_code(errc::invalid_argumen);//errc::invalid_argumen为标准错误枚举值
	//make_error_code() 函数将这个枚举值转换为 error_code 对象
	cout<<"错误码:"<<ec.value()<<endl;
	cout<<"错误信息:"<<ec.message()<<endl;
	cout<<"错误类别:"<<ec.category()<<endl;//category区分错误来源(如系统调用错误、自定义错误等)。
}

👉 好处:比异常更轻量,不中断程序执行。
👉 用法:标准库 I/O、文件系统(C++17 )常用。

5.3协程coroutine (C++20 正式标准化,C++11/14/17有experimental)

5.3.1 问题背景

  • 线程开销大(上下文切换耗时)。
  • 协程是“轻量级线程”,可以在一个线程里挂起和恢复函数执行。
  • 特别适合:异步I/O,事件驱动,任务调度。

5.3.2 C++20协程示例

#include <iostream>
#include <coroutine>
using namespace std;

struct Task {
    struct promise_type {
        Task get_return_object() { return {}; } //返回协程的结果对象(这里返回 Task 实例)。
        suspend_never initial_suspend() { return {}; }// 指定协程启动后是否立即暂停。suspend_never{} 表示不暂停,协程会立即开始执行。
        suspend_never final_suspend() noexcept { return {}; }//指定协程结束前是否暂停。suspend_never{} 表示不暂停,协程结束后直接退出。
        void return_void() {}//return_void():协程无返回值时的处理(对应 co_return;)。
        void unhandled_exception() {}//处理协程中未捕获的异常。
    };
};

Task myCoroutine() {
    cout << "协程开始" << endl;
    //执行 co_await suspend_always{}:suspend_always 表示必须暂停,此时协程会暂停执行,控制权返回给调用者(main 函数)。
    co_await suspend_always{};
    cout << "协程恢复" << endl;
}

int main() {
    auto t = myCoroutine();
    cout << "main 结束" << endl;
}

5.3.3协程的特点

  1. 用 co_return 返回值
  2. 用 co_yield 生成值(像 Python 的 yield 生成器)
  3. 用 co_await 挂起/恢复

5.3.4 协程 vs 线程

Column 1Column 2Column 2
特性协程线程
开销轻量级(栈共享)重(独立站+OS调度)
切换用户态(快)内核态(慢)
数量可达百万级通常几千个
场景I/O密集、异步CPU密集

小结

  1. std::thread:创建、join、detach,支持 lambda
  2. std::atomic:原子操作,解决数据竞争
  3. condition_variable:线程同步(生产者-消费者)
  4. thread_local:线程局部存储
  5. 1.异常处理
  • exception_ptr:跨线程传递异常
  • current_exception 捕获,rethrow_exception 抛出
  1. 2.错误处理
  • error_code:错误码对象,代替 errno
  • error_category:描述错误来源
  1. 3.协程
  • 轻量级线程
  • 三个关键字:co_return、co_yield、co_await
  • 适合异步 I/O / 任务调度

更多推荐