前言:
C++ 中,通过 thread_local
关键字来声明一个线程局部存储变量;同时,可以通过原子操作来保证变量的加载和存储操作;
现在,我们将通过 TLS 和 原子操作,实现高频率的 tid 获取程序;在此之前,说明一下使用 gettid() 的劣势,gettid() 涉及到系统调用,在高频系统中,会增加额外的开销(内核和系统之间来回切换);
步骤 1: 引入必要的库
我们需要 atomic
和 thread_local
来实现线程局部存储和原子操作:
#include <atomic>
#include <thread>
#include <unistd.h> // 用于 syscalls,例如 gettid()
步骤 2: 使用 thread_local
和 std::atomic
实现线程局部存储
在 C++ 中,我们可以将 cached_vtid
声明为线程局部存储的变量,并使用原子操作来进行加载和存储。
// 声明线程局部存储变量
thread_local std::atomic<pid_t> cached_vtid{0};// 用于清除缓存的函数,类似于 lttng_context_vtid_reset
void reset_cached_vtid() {cached_vtid.store(0, std::memory_order_release); // 清空缓存
}
步骤 3: 实现获取 TID 的函数
inline pid_t get_vtid() {pid_t vtid = cached_vtid.load(std::memory_order_acquire); // 获取缓存的 TIDif (vtid == 0) { // 如果缓存为空vtid = static_cast<pid_t>(syscall(SYS_gettid)); // 调用系统调用获取 TIDcached_vtid.store(vtid, std::memory_order_release); // 缓存新的 TID}return vtid; // 返回缓存或系统调用获取的 TID
}
步骤 4: 记录 TID
如果需要将 TID
存储到某个结构中,可以像以下方式进行操作:
void record_vtid(void* ctx, size_t offset) {pid_t vtid = get_vtid(); // 获取 TID// 假设我们有一个写入的操作// 这里可以替换为相应的 ring buffer 写入操作,假设是写入到 `chan` 中// 这里的 `ctx` 和 `offset` 是为了与原始代码保持一致// event_write(ctx, &vtid, sizeof(vtid), offset); // 假设写入操作
}
代码总结
#include <atomic>
#include <thread>
#include <unistd.h> // gettid// 线程局部存储,用于缓存 TID
thread_local std::atomic<pid_t> cached_vtid{0};// 用于清空缓存
void reset_cached_vtid() {cached_vtid.store(0, std::memory_order_release);
}// 获取线程 TID,采用原子操作避免系统调用
inline pid_t get_vtid() {pid_t vtid = cached_vtid.load(std::memory_order_acquire); // 获取缓存的 TIDif (vtid == 0) { // 如果缓存为空vtid = static_cast<pid_t>(syscall(SYS_gettid)); // 调用系统调用获取 TIDcached_vtid.store(vtid, std::memory_order_release); // 缓存新的 TID}return vtid; // 返回缓存或系统调用获取的 TID
}// 记录 TID
void record_vtid(void* ctx, size_t offset) {pid_t vtid = get_vtid(); // 获取 TID// 假设我们将 TID 写入某个缓冲区// event_write(ctx, &vtid, sizeof(vtid), offset); // 假设写入操作
}
解释
thread_local
:确保每个线程有自己的cached_vtid
变量,这样就可以避免线程间共享数据,确保线程安全。std::atomic<pid_t>
:std::atomic
确保对cached_vtid
的读写是原子的,不会出现竞态条件。syscall(SYS_gettid)
:调用gettid()
系统调用获取线程的 TID。由于我们在没有缓存时才调用系统调用,因此可以减少不必要的系统调用。
额外的注意点
- 如果程序有
fork
或clone
操作,可能需要清除缓存,因为这些操作会导致 TID 改变(特别是在子进程中)。reset_cached_vtid
可以在这些操作后调用,确保缓存被清空。 - 如果你的程序中使用了
liburcu
或其他线程同步机制,可能还需要结合特定的内存模型或同步原语来确保线程间的数据一致性。
这样,你就成功地将 C
中基于 URCU_TLS
的线程局部存储缓存机制迁移到了 C++ 中,利用了 thread_local
和 std::atomic
实现了线程安全的 TID 缓存。