线程特定存储(Thread-Specific Storage)详解
文章目录
一个 简单模拟线程局部存储的例子
#include <iostream>
#include <thread>
#include <vector>
#include <map>
#include <mutex>
#include <random>
// 线程特定的数据存储
class ThreadSpecificStorage {
std::mutex storage_mutex;
std::map<std::thread::id, int> storage;
public:
// 设置当前线程的数据
void set_data(int value) {
std::lock_guard<std::mutex> lock(storage_mutex);
storage[std::this_thread::get_id()] = value;
}
// 获取当前线程的数据
int get_data() {
std::lock_guard<std::mutex> lock(storage_mutex);
auto id = std::this_thread::get_id();
return storage.count(id) ? storage[id] : -1;
}
// 打印所有线程的数据
void print_all() {
std::lock_guard<std::mutex> lock(storage_mutex);
std::cout << "===== 线程数据存储 =====\n";
for (const auto& [id, value] : storage) {
std::cout << "线程 " << id << ": " << value << "\n";
}
}
};
// 全局存储实例
ThreadSpecificStorage tss;
void worker_task(int id) {
// 设置线程特定数据
tss.set_data(id * 10);
// 模拟工作
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(100, 500);
std::this_thread::sleep_for(std::chrono::milliseconds(dis(gen)));
// 获取并打印线程特定数据
std::cout << "工作线程 " << id << " 的数据: " << tss.get_data() << "\n";
}
int main() {
std::vector<std::thread> workers;
int num_workers = 5;
// 创建工作线程
for (int i = 0; i < num_workers; ++i) {
workers.emplace_back(worker_task, i);
}
// 主线程设置自己的数据
tss.set_data(999);
// 等待所有线程完成
for (auto& t : workers) {
t.join();
}
// 打印所有线程的数据
tss.print_all();
return 0;
}
这个示例完美体现了线程特定存储(Thread-Specific Storage, TSS)的概念,因为它实现了以下关键特性:
为什么这是线程特定存储的体现
1. 全局访问点,线程私有数据
// 全局存储实例
ThreadSpecificStorage tss;
所有线程都访问同一个全局对象
tss
但每个线程在
tss
中存储的数据是私有的,只属于该线程这就像公司有一个共享的储物柜系统,但每个员工有自己的专属格子
2. 基于线程ID的数据隔离
void set_data(int value) {
std::lock_guard<std::mutex> lock(storage_mutex);
storage[std::this_thread::get_id()] = value;
}
使用
std::this_thread::get_id()
作为键值每个线程的数据通过其唯一ID隔离
线程A无法访问线程B的数据,反之亦然
3. 线程本地上下文
void worker_task(int id) {
// 设置线程特定数据
tss.set_data(id * 10);
// ...执行工作...
// 获取线程特定数据
std::cout << "工作线程 " << id << " 的数据: " << tss.get_data() << "\n";
}
每个工作线程设置自己的数据(
id * 10
)在工作过程中,随时可以访问自己的数据
这类似于每个员工有自己的工作台和工具
4. 数据生命周期管理
// 主线程设置自己的数据
tss.set_data(999);
主线程和工作线程都有自己的数据存储
数据生命周期与线程执行周期相关联
线程结束后,其数据仍保留在存储中(直到被覆盖或清除)
5. 集中管理,分散存储
void print_all() {
std::lock_guard<std::mutex> lock(storage_mutex);
std::cout << "===== 线程数据存储 =====\n";
for (const auto& [id, value] : storage) {
std::cout << "线程 " << id << ": " << value << "\n";
}
}
集中管理所有线程的数据
但每个线程的数据是独立存储的
管理员(主线程)可以查看所有数据,但线程只能访问自己的
线程特定存储的标准实现
C++11引入了更高效的thread_local
关键字,这是标准化的线程特定存储:
#include <iostream>
#include <thread>
#include <vector>
// 使用thread_local实现线程特定存储
thread_local int thread_specific_data = 0;
void worker_task(int id) {
// 设置线程特定数据
thread_specific_data = id * 100;
// 模拟工作
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// 获取线程特定数据
std::cout << "线程 " << id << " 的数据: "
<< thread_specific_data << "\n";
}
int main() {
std::vector<std::thread> workers;
// 主线程设置自己的数据
thread_specific_data = 9999;
std::cout << "主线程初始数据: " << thread_specific_data << "\n";
// 创建工作线程
for (int i = 0; i < 5; ++i) {
workers.emplace_back(worker_task, i);
}
// 主线程数据保持不变
std::cout << "主线程数据未变: " << thread_specific_data << "\n";
for (auto& t : workers) {
t.join();
}
return 0;
}
线程特定存储的典型应用场景
1. 线程本地随机数生成器
#include <random>
#include <iostream>
#include <thread>
#include <vector>
// 线程特定的随机数引擎
thread_local std::mt19937 rng_engine(std::random_device{}());
void worker_task(int id) {
// 使用线程特定的随机数引擎
std::uniform_int_distribution<int> dist(1, 100);
int random_value = dist(rng_engine);
std::cout << "线程 " << id << " 的随机数: " << random_value << "\n";
}
int main() {
std::vector<std::thread> workers;
for (int i = 0; i < 5; ++i) {
workers.emplace_back(worker_task, i);
}
for (auto& t : workers) {
t.join();
}
return 0;
}
2. 数据库连接池(每个线程独立连接)
#include <iostream>
#include <thread>
#include <vector>
#include <memory>
class DatabaseConnection {
public:
DatabaseConnection(int id) : connection_id(id) {
std::cout << "创建数据库连接 " << connection_id << "\n";
}
void query(const std::string& sql) {
std::cout << "连接 " << connection_id << " 执行: " << sql << "\n";
}
~DatabaseConnection() {
std::cout << "关闭数据库连接 " << connection_id << "\n";
}
private:
int connection_id;
};
// 线程特定的数据库连接
thread_local std::unique_ptr<DatabaseConnection> db_conn;
void worker_task(int id) {
// 初始化线程特定的数据库连接
if (!db_conn) {
db_conn = std::make_unique<DatabaseConnection>(id);
}
// 使用连接执行查询
db_conn->query("SELECT * FROM users");
db_conn->query("UPDATE accounts SET balance = balance + 100");
// 连接在线程结束时自动关闭
}
int main() {
std::vector<std::thread> workers;
for (int i = 0; i < 3; ++i) {
workers.emplace_back(worker_task, i);
}
for (auto& t : workers) {
t.join();
}
return 0;
}
3. 请求上下文(Web服务器)
#include <iostream>
#include <thread>
#include <string>
#include <chrono>
// 请求上下文(线程特定)
thread_local std::string request_id;
thread_local std::chrono::steady_clock::time_point request_start_time;
void process_request(const std::string& id) {
// 设置请求上下文
request_id = id;
request_start_time = std::chrono::steady_clock::now();
// 模拟请求处理
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// 记录请求处理时间
auto duration = std::chrono::steady_clock::now() - request_start_time;
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
std::cout << "请求 " << request_id << " 处理时间: " << ms << "ms\n";
}
void handle_request(const std::string& id) {
try {
process_request(id);
} catch (...) {
std::cerr << "请求 " << request_id << " 处理失败\n";
}
}
int main() {
std::thread t1(handle_request, "req-001");
std::thread t2(handle_request, "req-002");
t1.join();
t2.join();
return 0;
}
线程特定存储的优势
避免锁竞争
每个线程访问自己的数据,无需同步
相比全局锁保护的共享数据,性能更高
简化API设计
函数无需传递上下文参数
代码更简洁,逻辑更清晰
自然隔离
线程间数据自动隔离
避免意外修改其他线程的状态
生命周期管理
数据随线程创建而创建
随线程结束而销毁(对于
thread_local
)
注意事项
初始化顺序
// 静态thread_local变量的初始化顺序不确定 thread_local int a = 10; thread_local int b = a + 5; // 可能未初始化
动态库问题
- 不同动态库中的
thread_local
变量可能不共享
- 不同动态库中的
性能考量
首次访问
thread_local
变量有初始化开销后续访问非常快(通常单条指令)
内存占用
每个线程都有变量的独立副本
对于大型对象,可能消耗较多内存
总结
线程特定存储(TSS)是一种强大的多线程编程模式:
提供全局访问点的线程私有数据
通过线程ID或
thread_local
关键字实现避免锁竞争,提高并发性能
适用于随机数生成器、数据库连接、请求上下文等场景
您提供的示例完美展示了TSS的核心概念:所有线程共享同一个存储系统,但每个线程在其中拥有自己独立的数据空间,通过线程ID进行隔离和访问。这种模式在多线程编程中非常有用,既能保持代码简洁,又能确保线程安全。