【C++多线程】thread_local 深度剖析:线程局部存储的生命周期与实现

前面的文章讨论了线程间的共享同步 ------互斥锁保护共享数据、条件变量协调线程间通信、原子操作保证共享变量的原子性。但有时候,我们恰恰需要相反的东西:每个线程拥有自己独立的数据副本 ,互不干扰,不需要同步。这就是线程局部存储(Thread Local Storage,TLS)。本文将剖析 thread_local 的存储期语义、编译器实现、TLS 表与线程控制块、动态初始化顺序问题,以及 TLS 在工业场景中的应用。


一、问题引入:为什么需要线程局部存储?

1.1 全局变量的并发问题

考虑一个错误码处理场景:

cpp 复制代码
int g_error_code = 0;  // 全局错误码

void do_work() {
    g_error_code = 0;
    // ... 执行操作 ...
    if (失败) {
        g_error_code = ERROR_CODE;
    }
}

void thread_func() {
    do_work();
    if (g_error_code != 0) {
        // 处理错误
    }
}

在多线程环境下,g_error_code 是全局共享的,一个线程设置的错误码可能被另一个线程覆盖:

复制代码
线程 A: g_error_code = ERROR_A
线程 B: g_error_code = ERROR_B(覆盖了 A 的错误码!)
线程 A: 检查 g_error_code → 看到的是 ERROR_B,不是自己的错误!

用互斥锁可以解决,但每次访问错误码都要加锁,性能差且代码繁琐。

1.2 线程局部存储的解决方案

线程局部存储(TLS)为每个线程提供独立的数据副本

cpp 复制代码
thread_local int t_error_code = 0;  // 每个线程有自己的副本

void do_work() {
    t_error_code = 0;  // 只修改当前线程的副本
    // ...
    if (失败) {
        t_error_code = ERROR_CODE;
    }
}

void thread_func() {
    do_work();
    if (t_error_code != 0) {  // 只读取当前线程的副本
        // 处理错误------一定是自己线程设置的
    }
}

每个线程访问 t_error_code 时,访问的是自己线程的独立副本,线程之间互不干扰,不需要同步。

TLS 的典型应用场景:

  • 错误码(errno、GetLastError)
  • 随机数生成器状态
  • 线程特定的配置/上下文
  • 性能计数器(避免伪共享)
  • 线程池中的工作线程状态
  • 单例模式的线程特定实例

二、C++ 中的四种存储期

在深入 thread_local 之前,先回顾 C++ 的四种存储期(storage duration):

存储期 关键字 生命周期 示例
自动存储期 (无,局部变量) 进入作用域创建,离开销毁 函数内的局部变量
静态存储期 static / extern 程序启动时创建,程序结束时销毁 全局变量、函数内 static 变量
线程存储期 thread_local 线程启动时创建,线程结束时销毁 thread_local 变量
动态存储期 new / delete 手动控制 堆上分配的对象

thread_local 变量的生命周期:

  • 创建:线程启动时(或首次访问时,取决于实现)
  • 初始化:首次访问时(动态初始化)或线程启动时(静态初始化)
  • 销毁:线程退出时,逆序销毁
  • 可见性:每个线程有独立副本,仅当前线程可访问自己的副本

三、API 速览:thread_local 的用法

3.1 基本用法

cpp 复制代码
// 全局 thread_local 变量
thread_local int counter = 0;

// 类的静态 thread_local 成员
class MyClass {
public:
    static thread_local int instance_count;
};
thread_local int MyClass::instance_count = 0;

// 函数内的 thread_local 变量
void func() {
    thread_local int call_count = 0;  // 每个线程首次调用时初始化
    call_count++;
}

3.2 thread_local 与 static 的组合

thread_local 可以和 static 组合使用(在类成员和函数内变量上):

cpp 复制代码
void func() {
    static thread_local int x = 0;  // 等价于 thread_local int x = 0
    // 在函数内,static 和 thread_local 可以同时出现,但 thread_local 隐含了 static
}

在命名空间作用域,thread_local 隐含 static(内部链接),除非显式声明为 extern

3.3 类的 thread_local 成员

cpp 复制代码
class ThreadSpecificData {
public:
    static thread_local ThreadSpecificData instance;  // 每个线程一个实例

    void do_something();
private:
    ThreadSpecificData() { /* 初始化 */ }
    ~ThreadSpecificData() { /* 清理 */ }
    // ...
};

// 定义(必须在类外)
thread_local ThreadSpecificData ThreadSpecificData::instance;

注意:thread_local 成员必须是 static 的------非静态成员变量的存储期由对象决定,不能是线程存储期。

3.4 thread_local 对象的构造与析构

cpp 复制代码
class Logger {
public:
    Logger() {
        std::cout << "Logger 构造,线程: " << std::this_thread::get_id() << std::endl;
    }
    ~Logger() {
        std::cout << "Logger 析构,线程: " << std::this_thread::get_id() << std::endl;
    }
    void log(const std::string& msg);
};

thread_local Logger t_logger;  // 每个线程有自己的 Logger

void thread_func() {
    t_logger.log("线程开始");  // 首次访问时构造
    // ...
    t_logger.log("线程结束");
}  // 线程退出时析构

输出:

复制代码
Logger 构造,线程: [线程A ID]
Logger 构造,线程: [线程B ID]
Logger 析构,线程: [线程B ID]
Logger 析构,线程: [线程A ID]

四、底层原理:TLS 的实现机制

4.1 TLS 的整体架构

线程局部存储的核心问题:同一个变量名,在不同线程中指向不同的内存地址

实现 TLS 需要解决:

  1. 存储空间分配:为每个线程的每个 TLS 变量分配独立的存储空间
  2. 地址解析 :访问 thread_local 变量时,如何找到当前线程的对应副本
  3. 初始化与销毁:何时构造、何时析构

主流操作系统的 TLS 架构:

复制代码
┌─────────────────────────────────────────┐
│              线程控制块(TCB)            │
│  ┌───────────────────────────────────┐  │
│  │         TLS 表(TLS Array)       │  │
│  │  [0] → TLS 模块1 的数据           │  │
│  │  [1] → TLS 模块2 的数据           │  │
│  │  [2] → ...                        │  │
│  └───────────────────────────────────┘  │
│  栈、寄存器、线程 ID 等其他线程状态       │
└─────────────────────────────────────────┘

每个线程有自己的线程控制块(TCB),TCB 中包含一个 TLS 表,表中每个条目指向一个 TLS 数据块。访问 thread_local 变量时,通过当前线程的 TCB 找到对应的 TLS 数据块,再计算变量在数据块中的偏移。

4.2 Linux 的 TLS 实现

在 Linux 上,TLS 通过 段寄存器(FS/GS)TLS 描述符实现。

x86-64 上,FS 段寄存器指向当前线程的 TCB(Thread Control Block),TCB 的开头是 tcbhead_t 结构,其中包含 TLS 数据的指针。

访问 thread_local 变量时,编译器生成类似这样的代码:

asm 复制代码
; 访问 thread_local int x
mov rax, fs:0          ; 获取 TCB 指针(FS 段基址)
mov rax, [rax + 0x28] ; 获取 TLS 表指针(DTV,Dynamic Thread Vector)
mov rax, [rax + 8]     ; 获取模块 1 的 TLS 数据块指针
mov eax, [rax + 4]     ; 读取变量 x(偏移 4)

更具体地说,Linux 的 TLS 使用 DTV(Dynamic Thread Vector) 机制:

复制代码
TCB (FS 段基址)
  └─ DTV 指针
       ├─ DTV[0] → 保留(存储 DTV 大小等元信息)
       ├─ DTV[1] → 模块 1 的 TLS 数据块(主程序的 TLS 变量)
       ├─ DTV[2] → 模块 2 的 TLS 数据块(某个 .so 的 TLS 变量)
       └─ DTV[n] → ...

每个动态链接模块(可执行文件或 .so)在编译时确定自己的 TLS 变量布局(变量在 TLS 数据块中的偏移),并分配一个模块 ID。运行时,每个线程为每个模块分配一个 TLS 数据块,存储在 DTV 中。

静态 TLS vs 动态 TLS

  • 静态 TLS(Initial Exec / Local Exec):主程序和启动时加载的库的 TLS 变量,线程创建时就分配好存储空间,访问速度快(一条指令)
  • 动态 TLS(General Dynamic / Local Dynamic) :运行时加载的库(dlopen)的 TLS 变量,首次访问时才分配,需要通过函数调用(__tls_get_addr)解析地址,速度稍慢

4.3 Windows 的 TLS 实现

Windows 的 TLS 通过 TEB(Thread Environment Block) 和 **TLS 槽位(TLS Slots)**实现。

每个线程的 TEB 中有一个 TLS 数组(TlsSlots),共 64 个槽位( minimum,可扩展)。DLL 可以通过 TlsAlloc() 分配一个槽位索引,然后用 TlsSetValue(index, value)TlsGetValue(index) 存储/读取线程特定数据。

C++ 的 thread_local 在 Windows 上的实现:

  • 静态 thread_local 变量 :编译器在 PE 文件的 .tls 段中定义变量布局,线程创建时分配并初始化
  • 访问方式 :通过 FS 段寄存器(x86)或 GS 段寄存器(x64)指向 TEB,TEB 中指向 TLS 数据块
  • 动态初始化 :首次访问时通过 _tls_used 回调机制初始化

4.4 thread_local 变量的地址解析

一个关键问题:thread_local 变量的地址在编译时是未知的(因为每个线程有不同的副本),那么 &t_variable 是如何工作的?

答案:每次取地址时,动态计算当前线程副本的地址

cpp 复制代码
thread_local int x = 0;

int* get_x_address() {
    return &x;  // 每次调用都计算当前线程的 x 的地址
}

int main() {
    int* p1 = get_x_address();  // 主线程的 x 地址

    std::thread t([&]() {
        int* p2 = get_x_address();  // 子线程的 x 地址
        // p1 != p2(不同线程的副本)
    });
    t.join();
}

编译器为 thread_local 变量的访问生成特殊的地址计算代码(通过段寄存器或 __tls_get_addr),而不是使用普通的静态地址。


五、动态初始化与销毁顺序

5.1 静态初始化 vs 动态初始化

和普通静态变量一样,thread_local 变量的初始化分为两种:

静态初始化(Static Initialization)

  • 编译期常量初始化,零初始化
  • 不需要运行时执行代码
  • 线程创建时,TLS 数据块直接从模板拷贝(内存拷贝)
cpp 复制代码
thread_local int x = 0;                  // 零初始化,静态初始化
thread_local int y = 42;                 // 常量初始化,静态初始化
thread_local const char* s = "hello";    // 常量初始化,静态初始化

动态初始化(Dynamic Initialization)

  • 需要运行时执行代码(调用构造函数、计算表达式)
  • 首次访问时执行(惰性初始化)
cpp 复制代码
thread_local std::string str = "hello";  // 调用 string 构造函数,动态初始化
thread_local int z = compute_value();     // 调用函数,动态初始化
thread_local MyClass obj;                 // 调用默认构造函数,动态初始化

5.2 动态初始化的线程安全

C++ 标准规定:thread_local 变量的动态初始化是线程安全 的------如果多个线程同时首次访问同一个 thread_local 变量,只有一个线程执行初始化,其他线程阻塞等待。

等等,这里需要澄清:每个线程有自己的 thread_local 副本,所以"多个线程同时首次访问"实际上是每个线程初始化自己的副本,它们之间没有竞争。这里的"线程安全"是指:同一个线程内,多次首次访问的竞态(如递归调用、信号处理),以及实现内部的初始化保护。

实际上,由于每个线程独立初始化自己的副本,thread_local 变量的动态初始化天然没有跨线程竞争。但同一个线程内可能因为递归调用而出现重入问题,标准要求实现处理这种情况(类似函数局部静态变量的初始化保护)。

5.3 销毁顺序

thread_local 变量在线程退出时销毁,销毁顺序是逆初始化顺序(和静态变量的销毁顺序类似):

复制代码
线程启动
  ↓
首次访问 A → 构造 A
  ↓
首次访问 B → 构造 B
  ↓
首次访问 C → 构造 C
  ↓
线程退出
  ↓
析构 C(逆序)
  ↓
析构 B
  ↓
析构 A

注意:只有被构造过thread_local 变量才会被析构。如果一个线程从未访问某个 thread_local 变量,该变量的副本不会被构造,也不会被析构。

5.4 销毁顺序问题(ODR-use)

和静态变量一样,thread_local 变量的销毁顺序也可能导致问题:如果一个 thread_local 对象的析构函数依赖另一个已经析构的 thread_local 对象,会出现未定义行为。

cpp 复制代码
thread_local Logger t_logger;        // 先构造
thread_local Resource t_resource;     // 后构造

// 线程退出时,先析构 t_resource,再析构 t_logger
// 如果 t_resource 的析构函数使用 t_logger,没问题(t_logger 还在)
// 但如果 t_logger 的析构函数使用 t_resource,就有问题(t_resource 已析构)

避免方法:

  1. 减少 thread_local 对象之间的依赖
  2. 用指针手动控制生命周期
  3. 接受"线程退出时不严格析构"(很多场景下,线程退出时资源由操作系统回收)

六、源码剖析:编译器如何实现 thread_local

6.1 GCC/Clang 的 TLS 模型

GCC/Clang 支持四种 TLS 模型(通过 -ftls-model 选项指定):

模型 名称 适用场景 访问开销
local-exec Local Executable 主程序中的 TLS 变量 最低(1-2 条指令)
initial-exec Initial Executable 启动时加载的库中的 TLS 低(2-3 条指令)
local-dynamic Local Dynamic 同一库内的 TLS 变量 中(一次函数调用)
global-dynamic Global Dynamic 任意 TLS 变量(默认) 最高(一次函数调用)

编译器会根据变量的位置(主程序还是库)和可见性选择最优模型。

6.2 local-exec 模型的汇编

对于主程序中的 thread_local 变量,x86-64 上生成的代码非常简洁:

asm 复制代码
; thread_local int x = 42;
; 读取 x
mov eax, DWORD PTR fs:x@tpoff   ; FS 段基址 + 偏移,一条指令!

; 写入 x
mov DWORD PTR fs:x@tpoff, 42    ; 同样一条指令

fs: 前缀表示使用 FS 段寄存器的基址(指向当前线程的 TCB),x@tpoff 是变量在 TLS 数据块中的偏移量(编译时确定)。整条访问只需要一条指令,和访问普通全局变量几乎一样快。

6.3 global-dynamic 模型的汇编

对于动态库中的 thread_local 变量,需要通过 __tls_get_addr 函数解析地址:

asm 复制代码
; thread_local int x;(在动态库中)
; 读取 x
lea rdi, [x@tlsgd]           ; 加载 TLS 描述符地址
call __tls_get_addr@PLT      ; 调用函数获取当前线程的变量地址
mov eax, DWORD PTR [rax]      ; 读取值

__tls_get_addr 内部会:

  1. 检查当前线程的 DTV 中是否已分配该模块的 TLS 数据块
  2. 如果没有,分配并从模板初始化
  3. 返回变量在当前线程副本中的地址

这种方式需要一次函数调用,开销比 local-exec 大,但支持动态加载的库。

6.4 MSVC 的实现

MSVC 用 __declspec(thread) 声明线程局部变量(C++11 的 thread_local 在 MSVC 中等价于 __declspec(thread)):

cpp 复制代码
__declspec(thread) int x = 42;  // MSVC 风格
thread_local int y = 42;         // C++11 标准风格(MSVC 中等价)

访问时通过 FS(x86)或 GS(x64)段寄存器:

asm 复制代码
; x86: FS 指向 TEB
mov eax, DWORD PTR fs:x          ; 一条指令
; x64: GS 指向 TEB
mov eax, DWORD PTR gs:y          ; 一条指令

MSVC 的 TLS 变量存储在 PE 文件的 .tls 段中,定义了初始化模板。线程创建时,系统为每个线程分配 TLS 数据并从 .tls 段拷贝初始化。


七、工程实践

7.1 用 thread_local 避免伪共享

在高并发计数器场景中,thread_local 是避免伪共享的有效手段:

cpp 复制代码
// ❌ 全局原子计数器:高竞争 + 伪共享
std::atomic<int64_t> g_counter{0};

void on_event() {
    g_counter.fetch_add(1, std::memory_order_relaxed);
}

// ✅ thread_local 计数器 + 定期汇总
thread_local int64_t t_counter = 0;  // 每个线程独立计数,无竞争

void on_event() {
    t_counter++;  // 纯局部操作,极快
}

int64_t get_total() {
    // 需要汇总时,遍历所有线程的计数器(需要维护线程列表)
    // 或者用一个原子变量定期累加
}

更完善的实现:每个线程维护本地计数器,定期将本地计数累加到全局原子计数器,然后清零。这样既避免了每次操作的竞争,又能获取全局总数。

7.2 用 thread_local 实现线程特定的单例

cpp 复制代码
class ThreadLocalSingleton {
public:
    static ThreadLocalSingleton& instance() {
        thread_local ThreadLocalSingleton inst;  // 每个线程一个实例
        return inst;
    }

    void do_work();

private:
    ThreadLocalSingleton() = default;
    ~ThreadLocalSingleton() = default;
    ThreadLocalSingleton(const ThreadLocalSingleton&) = delete;
    ThreadLocalSingleton& operator=(const ThreadLocalSingleton&) = delete;

    // 线程特定的数据
    std::vector<int> local_cache_;
};

每个线程调用 instance() 得到自己的独立实例,不需要同步。适用于每个线程需要独立状态的场景(如线程池中的工作线程上下文)。

7.3 用 thread_local 实现错误码

cpp 复制代码
class ErrorCode {
public:
    static int& get() {
        thread_local int code = 0;  // 每个线程独立的错误码
        return code;
    }

    static void set(int code) {
        get() = code;
    }

    static void clear() {
        get() = 0;
    }
};

// 使用
void do_operation() {
    ErrorCode::clear();
    // ... 执行操作 ...
    if (失败) {
        ErrorCode::set(ERROR_INVALID_PARAM);
    }
}

void thread_func() {
    do_operation();
    if (ErrorCode::get() != 0) {
        // 只看到自己线程的错误码
    }
}

这就是 C 标准库 errno 和 Windows GetLastError 的实现原理------它们都是线程局部变量。

7.4 常见陷阱

陷阱1:thread_local 变量的地址跨线程使用

cpp 复制代码
thread_local int x = 0;

int* ptr = nullptr;

void thread1() {
    x = 42;
    ptr = &x;  // 保存线程1的 x 地址
}

void thread2() {
    std::cout << *ptr << std::endl;  // ❌ 访问线程1的 x 地址!
    // 这是未定义行为------ptr 指向线程1的栈/TLS区域,线程2访问可能崩溃
}

thread_local 变量的地址是线程特定的,不能跨线程传递和访问。如果需要跨线程共享数据,用普通变量 + 同步机制。

陷阱2:在 thread_local 对象的析构中访问已销毁的对象

cpp 复制代码
thread_local Logger t_logger;
thread_local Resource t_resource;

// 如果 t_logger 先构造,t_resource 后构造
// 析构时 t_resource 先析构,t_logger 后析构
// 如果 t_logger 的析构函数访问 t_resource → 未定义行为

和静态变量一样,thread_local 对象的析构顺序问题需要注意。尽量减少 thread_local 对象之间的依赖。

陷阱3:动态库卸载后的 thread_local 访问

如果一个 thread_local 变量定义在动态库中,当动态库被 dlclose 卸载后,已分配的 TLS 数据可能变成悬空指针。访问已卸载库的 thread_local 变量是未定义行为。

实际工程中,应该避免在运行时频繁加载/卸载包含 thread_local 变量的库,或者确保卸载前所有线程都已完成对该库 TLS 变量的访问。

陷阱4:thread_local 与线程池

在线程池中,线程是复用的。thread_local 变量在线程复用时不会自动重置------上一个任务设置的值会保留到下一个任务。

cpp 复制代码
thread_local int t_request_id = 0;

// 线程池任务
void handle_request(int id) {
    // ❌ 没有重置 t_request_id,上一个任务的值可能残留
    if (t_request_id == 0) {
        t_request_id = id;
    }
    // ...
}

在线程池中使用 thread_local 时,必须在任务开始时显式重置,或在任务结束时清理。

7.5 thread_local 的性能特征

操作 开销(x86-64,local-exec 模型)
读取 thread_local 变量 ~1ns(一条指令,和全局变量几乎一样)
写入 thread_local 变量 ~1ns
取地址(&variable) ~1ns
首次访问(动态初始化) ~100ns-1μs(构造函数开销)
线程创建(静态 TLS 分配) ~1-10μs(取决于 TLS 变量数量)
线程退出(TLS 析构) ~1-10μs(取决于构造过的对象数量)

在静态 TLS 模型下,thread_local 变量的访问性能和普通全局变量几乎相同,非常高效。主要开销在线程创建/退出时的 TLS 数据分配/销毁,以及首次访问时的动态初始化。


八、面试高频问题

Q1:什么是线程局部存储(TLS)?thread_local 的作用是什么?

线程局部存储(Thread Local Storage,TLS)为每个线程提供独立的数据副本,线程之间互不干扰,不需要同步。C++11 引入 thread_local 关键字声明线程存储期变量,其生命周期从线程启动(或首次访问)到线程退出。典型应用包括:错误码(errno/GetLastError 的实现原理)、随机数生成器状态、线程特定配置/上下文、性能计数器(避免伪共享和竞争)、线程池工作线程状态等。thread_local 变量每个线程有独立副本,访问时通过当前线程的控制块找到对应副本地址。

Q2:C++ 有哪四种存储期?thread_local 的生命周期是怎样的?

C++ 四种存储期:(1)自动存储期------局部变量,进入作用域创建、离开销毁;(2)静态存储期------static/extern 变量,程序启动时创建、结束时销毁;(3)线程存储期------thread_local 变量,线程启动时创建、线程退出时销毁;(4)动态存储期------new/delete 手动管理。thread_local 变量的生命周期:创建于线程启动时(静态初始化)或首次访问时(动态初始化),销毁于线程退出时(逆初始化顺序析构)。只有被构造过的副本才会被析构,从未访问的变量不会构造也不会析构。

Q3:thread_local 变量的底层实现原理?如何实现每个线程独立副本?

TLS 的核心是通过线程控制块(TCB)中的 TLS 表实现地址重定向。每个线程有自己的 TCB,TCB 中包含 TLS 表(或 DTV 动态线程向量),表中每个条目指向该线程的一个 TLS 数据块。访问 thread_local 变量时,编译器生成特殊代码:通过段寄存器(Linux x86-64 用 FS,Windows x64 用 GS)找到当前线程的 TCB,再通过 TLS 表找到对应模块的 TLS 数据块,最后用编译时确定的偏移访问变量。静态 TLS(主程序/启动时加载的库)访问只需 1-2 条指令,性能接近全局变量;动态 TLS(dlopen 加载的库)需要 __tls_get_addr 函数调用解析地址,开销稍大。

Q4:thread_local 变量的动态初始化是线程安全的吗?销毁顺序是怎样的?

thread_local 变量的初始化分为静态初始化(常量/零初始化,线程创建时内存拷贝)和动态初始化(需要执行代码,首次访问时惰性初始化)。由于每个线程独立初始化自己的副本,动态初始化天然没有跨线程竞争,但标准要求实现处理同一线程内的重入问题(类似函数局部静态变量的保护)。销毁顺序:线程退出时,按逆初始化顺序析构(先构造的后析构),只有被构造过的变量才会析构。和静态变量一样,thread_local 对象的析构顺序可能导致依赖问题------如果一个对象的析构函数访问另一个已析构的 thread_local 对象,是未定义行为。

Q5:thread_local 在线程池中有什么陷阱?

线程池中的线程是复用的,thread_local 变量在线程复用时不会自动重置------上一个任务设置的值会残留到下一个任务。例如,一个任务在 thread_local 中存储了请求 ID,线程复用时新任务可能读到旧的请求 ID。因此在线程池中使用 thread_local 时,必须在任务开始时显式重置(或在任务结束时清理)。这是 thread_local 最常见的工程陷阱之一。另一个相关问题是:thread_local 变量的析构发生在线程真正退出时,而不是任务结束时,长生命周期的线程池线程可能长期持有 thread_local 对象不释放。

Q6:thread_local 和 static 的区别?可以组合使用吗?

区别:(1)生命周期------static 变量是程序级的(整个程序一份),thread_local 是线程级的(每个线程一份);(2)可见性------static 变量被所有线程共享(需要同步),thread_local 变量线程间独立(不需要同步);(3)销毁时机------static 在程序结束时销毁,thread_local 在线程退出时销毁。可以组合使用:在函数内或类成员中,static thread_local 是合法的,此时 static 表示链接性/作用域(而非存储期),thread_local 决定存储期。在命名空间作用域,thread_local 隐含 static(内部链接),除非显式声明为 extern。类的 thread_local 成员必须是 static 的------非静态成员的存储期由对象决定,不能是线程存储期。


下一篇预告:第 12 篇《死锁、活锁、饥饿与优先级反转:并发问题的识别与规避》。我们将从理论和实践两个维度剖析并发编程中的四大经典问题------死锁的四必要条件与银行家算法、活锁的退避策略、饥饿的成因与公平性、优先级反转与优先级继承/天花板协议,并给出工业场景中的识别方法和规避策略。

相关推荐
whitelbwwww1 小时前
c++ 多线程
开发语言·c++·算法
努力努力再努力wz1 小时前
【Docker入门系列】:从架构演进到容器化:一文建立 Docker、虚拟化与 Namespace 的底层心智模型
运维·开发语言·数据结构·c++·docker·容器·架构
光电笑映2 小时前
Linux 线程编程:从进程、分页到线程控制与封装
linux·运维·服务器·c++
qinzechen2 小时前
本周科技行业热点汇总·2026第36周(2026年8月31日-9月6日)
c++·科技·算法
aqiu1111112 小时前
【C++ 代码分析与重构】常见 DFS 逻辑错误解析与修正
c++·重构·深度优先
zuozong_2 小时前
C++类与对象
开发语言·c++·算法
hansang_IR2 小时前
【代码】分层最短路模板
c++·算法·最短路
暖焰核心2 小时前
C++模板进阶——特化全解
javascript·c++·jquery
励志不掉头发的内向程序员2 小时前
【LibreCAD 2D架构】从两个坐标到图形实体:RS_ActionDrawLine如何创建RS_Line
开发语言·c++·qt·学习·系统架构