线程本地存储

本节阅读量:

在多线程程序中,有时候我们希望每个线程都拥有自己独立的数据副本,而不是与其他线程共享。这就好比每个人都有自己的储物柜,放进去的东西只有自己能拿到,互不干扰。


为什么需要线程本地存储

考虑这样一个场景:我们需要统计每个线程处理了多少个任务。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <thread>
#include <vector>

int task_count = 0;  // 所有线程共享的计数器

void worker(int thread_id, int task_num) {
    for (int i = 0; i < task_num; ++i) {
        // 模拟处理任务
        task_count++;  // 所有线程同时修改这个变量
    }
    std::cout << "Thread " << thread_id << " processed " << task_count << " tasks\n";
}

int main() {
    std::thread t1(worker, 1, 100);
    std::thread t2(worker, 2, 200);
    
    t1.join();
    t2.join();
    
    std::cout << "Total tasks: " << task_count << std::endl;
    return 0;
}

这个程序的问题是:

  1. task_count 被所有线程共享,存在数据竞争
  2. 每个线程输出的 “processed X tasks” 并不是自己实际处理的任务数
  3. 最后输出的总数可能也不正确

当然,我们可以用锁来保护 task_count,但这样每个线程输出的仍然不是自己想要的结果——它们看到的是共享的累计值,而非各自的贡献。


使用 thread_local

C++11 引入了 thread_local 关键字,用于声明线程本地存储的变量。每个线程都有自己独立的副本,互不影响。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include <thread>
#include <vector>

thread_local int task_count = 0;  // 每个线程有自己的副本

void worker(int thread_id, int task_num) {
    for (int i = 0; i < task_num; ++i) {
        // 模拟处理任务
        task_count++;  // 只修改自己的副本
    }
    std::cout << "Thread " << thread_id << " processed " << task_count << " tasks\n";
}

int main() {
    std::thread t1(worker, 1, 100);
    std::thread t2(worker, 2, 200);
    
    t1.join();
    t2.join();
    
    // main 线程的 task_count 仍然是 0
    std::cout << "Main thread task_count: " << task_count << std::endl;
    return 0;
}

输出:

1
2
3
Thread 1 processed 100 tasks
Thread 2 processed 200 tasks
Main thread task_count: 0

每个线程都有自己的 task_count,修改时互不干扰。主线程的 task_count 保持初始值 0,因为它也有自己的副本。


thread_local 的生命周期

thread_local 变量的生命周期与线程绑定:

  1. 创建时机:当线程首次使用到thread_local 对象时(首次引用/调用),才会初始化对应的对象
  2. 销毁时机:当线程结束时,该线程的 thread_local 变量副本会被销毁
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
#include <thread>

class Counter {
public:
    Counter() {
        std::cout << "Counter created in thread\n";
    }
    ~Counter() {
        std::cout << "Counter destroyed in thread\n";
    }
    int count = 0;
};

void worker() {
    thread_local Counter counter;  // 每个线程第一次执行到这里时创建
    counter.count++;
    std::cout << "Count: " << counter.count << std::endl;
}

int main() {
    std::cout << "=== Starting thread 1 ===\n";
    std::thread t1([]() {
        worker();  // Counter 创建
        worker();  // Counter 已存在,复用
    });
    t1.join();
    
    std::cout << "=== Starting thread 2 ===\n";
    std::thread t2(worker);  // 新的线程,创建新的 Counter
    t2.join();
    
    return 0;
}

输出:

1
2
3
4
5
6
7
8
9
=== Starting thread 1 ===
Counter created in thread
Count: 1
Count: 2
Counter destroyed in thread
=== Starting thread 2 ===
Counter created in thread
Count: 1
Counter destroyed in thread

典型应用场景

1. 线程专属的缓存

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <thread>
#include <vector>
#include <random>

thread_local std::mt19937 rng;  // 每个线程有自己的随机数生成器

void generate_random() {
    // 初始化种子(只在线程第一次执行时)
    static thread_local bool initialized = false;
    if (!initialized) {
        rng.seed(std::random_device{}());
        initialized = true;
    }
    
    std::uniform_int_distribution<int> dist(1, 100);
    std::cout << "Random: " << dist(rng) << std::endl;
}

2. 线程专属的日志记录器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
#include <sstream>
#include <thread>
#include <mutex>

std::mutex log_mutex;

class ThreadLogger {
public:
    void log(const std::string& msg) {
        buffer_ << "[Thread " << std::this_thread::get_id() << "] " << msg << "\n";
    }
    
    void flush() {
        std::lock_guard<std::mutex> lock(log_mutex);
        std::cout << buffer_.str();
        buffer_.str("");
        buffer_.clear();
    }
    
private:
    std::ostringstream buffer_;
};

thread_local ThreadLogger logger;  // 每个线程有自己的日志缓冲区

void worker(int id) {
    logger.log("Starting task " + std::to_string(id));
    // 处理任务...
    logger.log("Finished task " + std::to_string(id));
    logger.flush();  // 批量输出,减少锁竞争
}


0.8 如何充分发挥多线程的性能

上一节

0.10 使用 shared_ptr 管理多线程内存

下一节