C++11 起可以用 std::thread 在同一进程里再跑一条执行流,以便利用多核。两条流若无协调地改同一变量,就是数据竞争,行为未定义。构造 thread 时任务立刻开始;析构前必须 join() 等它结束,或 detach() 分离,否则程序会 std::terminate。任务级稍后取结果见async 与 future。lambda 写法见Lambda。

创建与 join

下面一条线程打印 hello,主线程 join 之后再继续。向线程传引用参数须用 std::ref,否则会按值拷贝。

C++ thread 与 join运行
#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:

C++ mutex 保护计数运行
#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:通过标准库提交异步任务并获取结果。