← SEARCHABLE ARCHIVE
C / C++ · 2026-08-14

从 C++ 基础到掌握 — 第 20 集

使用线程、atomic 和内存模型处理并发

INTERACTIVE READING MODE →

本博客文章以教学为目的编写。 内容可能存在错误,请结合目标编译器和标准库文档进行确认。

多个线程访问同一内存时,问题不只是执行顺序不固定。按照 C++ memory model,未同步的 data race 是 undefined behavior。

明确保护共享不变量

class Inventory
{
public:
    void Add(Item item)
    {
        std::lock_guard lock(Mutex);
        Items.push_back(std::move(item));
    }

private:
    std::mutex Mutex;
    std::vector<Item> Items;
};

std::lock_guard 在离开作用域时释放 mutex,包括异常路径。锁应保护完整不变量,而不只是一次 vector 调用。

以不同顺序获取多个 mutex 可能 deadlock。可以用 std::scoped_lock 一起锁定,或制定项目统一的加锁顺序。

Atomic 适合小型独立值

std::atomic<unsigned int> completedJobs{0};

void MarkCompleted()
{
    completedJobs.fetch_add(1, std::memory_order_relaxed);
}

不负责发布其他状态的统计计数器可能只需 relaxed ordering。但一个 atomic 计数器不会自动保护多字段对象的一致性。

默认 sequential consistency 更容易推理。acquire、release、relaxed 等较弱顺序应建立在已证明的 happens-before 关系上,而不是性能猜测。

用 jthread 绑定结束生命周期

void Worker(std::stop_token stop)
{
    while (!stop.stop_requested())
        ProcessOneJob();
}

void Run()
{
    std::jthread worker(Worker);
    // 析构时请求停止并 join。
}

C++20 std::jthread 在析构时请求停止并等待线程。stop_token 是协作式取消;无法响应的 blocking I/O 仍需 timeout 或唤醒机制。

更好的第一步是移除共享修改状态。给 worker 独立输入,再通过消息或 queue 收集结果。不同 atomic 若位于同一 cache line,也可能因 false sharing 变慢。

一次测试成功不能证明并发代码安全。应记录所有权、结束条件、锁顺序和数据发布规则,并用 ThreadSanitizer 与可复现压力测试验证。