C++11 起可以用 std::thread 在同一进程里再跑一条执行流,以便利用多核。两条流若无协调地改同一变量,就是数据竞争,行为未定义。构造 thread 时任务立刻开始;析构前必须 join() 等它结束,或 detach() 分离,否则程序会 std::terminate。任务级稍后取结果见async 与 future。lambda 写法见Lambda。
创建与 join
下面一条线程打印 hello,主线程 join 之后再继续。向线程传引用参数须用 std::ref,否则会按值拷贝。
#include <iostream>
#include <thread>
int main() {
std::thread t([]() { std::cout << "hello\n"; });
t.join();
return 0;
}输出:
hello两条线程同时写 std::cout 也可能交错,需要互斥或等 join 后再由主线程统一打印。
数据竞争
至少一个线程在写、另有线程在读或写同一位置、且中间没有同步,就是数据竞争。下面这种无锁自增,结果不确定,不要这么写:
int counter = 0;
void bad_inc() {
for (int i = 0; i < 100000; ++i) {
++counter; // 无锁,错误
}
}mutex 与 lock_guard
std::mutex 独占锁。lock_guard 构造时加锁、析构时解锁,避免忘记 unlock。下面两个线程各加 1000 次,有锁则合计 2000:
#include <iostream>
#include <mutex>
#include <thread>
std::mutex m;
int counter = 0;
void inc() {
for (int i = 0; i < 1000; ++i) {
std::lock_guard<std::mutex> lock(m);
++counter;
}
}
int main() {
std::thread a(inc), b(inc);
a.join();
b.join();
std::cout << counter << std::endl;
return 0;
}输出:
2000- 临界区尽量短,只包住真正共享的那几行。
- 同一线程对同一非递归
mutex加第二次锁会卡住。 - 需要配合条件变量或延迟加锁时,再用
unique_lock。
了解:atomic
单个整数计数可以用 std::atomic<int>,++counter 是原子的。复杂结构仍须 mutex。内存序属于进阶主题,入门用默认即可。
本节说明了线程创建、汇合与互斥量保护共享数据的方式。下一节介绍async 与 future:通过标准库提交异步任务并获取结果。