一、原子操作与内存序:多线程同步的基石

在多线程编程中,最让人头疼的问题莫过于数据竞争。想象一下,两个线程同时修改同一个变量,结果会怎样?大概率会出现不可预测的行为。这时候,原子操作就像救世主一样出现了。

C++11 引入了 <atomic> 头文件,提供了一系列原子类型和操作。原子操作的核心特点是:不可分割,即操作要么完全执行,要么完全不执行,不会出现中间状态。

1.1 基本原子操作示例

#include <atomic>
#include <thread>
#include <iostream>

std::atomic<int> counter(0);  // 原子整型变量

void increment() {
    for (int i = 0; i < 1000; ++i) {
        counter.fetch_add(1, std::memory_order_relaxed);  // 原子递增
    }
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);

    t1.join();
    t2.join();

    std::cout << "Final counter value: " << counter << std::endl;  // 正确输出 2000
    return 0;
}

注释说明:

  • std::atomic<int> 确保 counter 的操作是原子的。
  • fetch_add 是原子加法操作,std::memory_order_relaxed 表示最宽松的内存序(稍后详解)。

1.2 内存序:控制指令执行顺序

原子操作不仅仅是“原子性”,还涉及内存可见性指令重排。C++ 提供了 6 种内存序:

  1. memory_order_relaxed:只保证原子性,不保证顺序。
  2. memory_order_consume:依赖关系的数据同步。
  3. memory_order_acquire:确保当前操作之后的读操作不会被重排到前面。
  4. memory_order_release:确保当前操作之前的写操作不会被重排到后面。
  5. memory_order_acq_relacquire + release
  6. memory_order_seq_cst:最严格的顺序一致性(默认)。
std::atomic<bool> flag(false);
int data = 0;

void producer() {
    data = 42;  // 普通写操作
    flag.store(true, std::memory_order_release);  // 确保 data 的写入在 flag 之前
}

void consumer() {
    while (!flag.load(std::memory_order_acquire));  // 确保 data 的读取在 flag 之后
    std::cout << "Data: " << data << std::endl;  // 正确读取 42
}

int main() {
    std::thread t1(producer);
    std::thread t2(consumer);

    t1.join();
    t2.join();
    return 0;
}

适用场景:

  • 计数器、标志位等简单同步场景可用 relaxed
  • 生产者-消费者模型适合 release-acquire 配对。
  • 复杂同步逻辑可能需要 seq_cst,但性能较低。

二、无锁队列:高性能并发数据结构

锁(如 mutex)虽然能解决同步问题,但会带来性能瓶颈。无锁(lock-free)数据结构通过原子操作实现线程安全,避免线程阻塞。

2.1 单生产者单消费者无锁队列

#include <atomic>

template<typename T>
class LockFreeQueue {
private:
    struct Node {
        T data;
        Node* next;
        Node(T val) : data(val), next(nullptr) {}
    };

    std::atomic<Node*> head;
    std::atomic<Node*> tail;

public:
    LockFreeQueue() : head(new Node(T())), tail(head.load()) {}

    void enqueue(T val) {
        Node* newNode = new Node(val);
        Node* oldTail = tail.load(std::memory_order_relaxed);
        while (!tail.compare_exchange_weak(oldTail, newNode, 
               std::memory_order_release, std::memory_order_relaxed)) {
            // CAS 失败,重试
        }
        oldTail->next = newNode;
    }

    bool dequeue(T& result) {
        Node* oldHead = head.load(std::memory_order_relaxed);
        Node* nextNode = oldHead->next;
        if (nextNode == nullptr) return false;  // 队列为空
        result = nextNode->data;
        head.store(nextNode, std::memory_order_relaxed);
        delete oldHead;
        return true;
    }
};

注释说明:

  • compare_exchange_weak 是 CAS(Compare-And-Swap)操作,核心无锁技术。
  • 此实现仅适用于单生产者单消费者场景。

2.2 多生产者多消费者无锁队列

更复杂的实现需要处理多线程竞争,通常结合 atomic 和内存序优化。

适用场景:

  • 高吞吐量任务队列(如日志系统)。
  • 实时数据处理(如金融交易系统)。

缺点:

  • 实现复杂,容易引入 bug。
  • 内存回收问题(如 ABA 问题)。

三、线程局部存储(TLS):线程私有数据优化

某些数据只需要在线程内部使用(如随机数种子、缓存),使用全局变量会导致竞争,而 thread_local 可以解决这个问题。

3.1 thread_local 基本用法

#include <thread>
#include <iostream>

thread_local int threadId = 0;  // 每个线程独立副本

void printId() {
    std::cout << "Thread ID: " << threadId << std::endl;
}

int main() {
    threadId = 1;
    std::thread t([]{ 
        threadId = 2; 
        printId();  // 输出 2
    });

    printId();  // 输出 1
    t.join();
    return 0;
}

适用场景:

  • 线程局部缓存(如数据库连接池)。
  • 避免锁竞争的线程私有数据。

四、总结与实战建议

  1. 原子操作:优先用于简单同步,注意内存序选择。
  2. 无锁队列:适合高性能场景,但实现难度高。
  3. 线程局部存储:优化线程私有数据访问。

注意事项:

  • 无锁编程容易引入隐蔽 bug,务必充分测试。
  • 内存序错误可能导致难以调试的问题。
  • 性能优化前先做 profiling,避免过早优化。