跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++

Linux 多线程编程:线程栈、TLS、互斥锁与条件变量详解

Linux 多线程编程涉及线程栈隔离、线程局部存储(TLS)、互斥锁及条件变量机制。线程栈为私有资源,全局数据共享但需保护。__thread 关键字实现线程私有全局变量。互斥锁解决临界区竞争问题,防止数据不一致。条件变量用于线程同步,避免忙等待,提高并发效率。通过抢票示例演示了竞态条件及加锁、同步解决方案。

筑梦师发布于 2026/2/7更新于 2026/10/2397 浏览
Linux 多线程编程:线程栈、TLS、互斥锁与条件变量详解

线程栈

在 Linux 系统中,没有很明确的线程概念,只有轻量级进程的概念。操作系统提供的系统调用不能直接创建线程,只能创建轻量级进程,该接口为 clone。

  • fn:指定子任务的执行入口函数。新创建的任务从该函数开始运行,函数返回时子任务结束。
  • child_stack:指向子任务使用的栈空间。由于 Linux 栈向低地址增长,该参数通常指向栈空间的高地址。系统调用要求手动为子任务分配栈空间,风险较高。pthread 库底层封装了此接口,提供默认方法避免手动分配。
  • flags:用于指定子任务与父任务共享的资源类型,是 clone 的核心参数。通过不同标志位组合,可以创建进程或线程。
  • arg:传递给子任务入口函数的参数,用于初始化子任务的运行环境。

pthread 是用户态的第三方库,通过动态链接方式加载到进程虚拟地址空间的共享区。除了主线程外,其他线程都需要独立的栈空间,用于保存函数调用过程中的局部变量、返回值地址及临时数据。若所有线程共享同一栈空间,易造成数据覆盖。因此线程栈是线程私有资源,而代码区、数据区及堆空间是共享的。pthread 维护的线程栈通过 clone 的 child_stack 参数传递给内核,保证线程栈独立性。

示例验证

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <string>
#include <vector>

struct threadData {
    std::string threadname;
};

void *threadRoutine(void *args) {
    threadData *td = (threadData *)args;
    int count = 5;
    while (count--) {
        printf("pid:%d, tid:%lx, threadname:%s\n", getpid(), pthread_self(), td->threadname.c_str());
        sleep(1);
    }
    return nullptr;
}

  {
    std::vector<> tids;
     ( i = ; i < ; i++) {
         tid;
        threadData *td =  threadData;
        td->threadname =  + std::(i);
        (&tid, , threadRoutine, td);
        tids.(tid);
    }
     ( i = ; i < ; i++) {
        (tids[i], );
    }
     ;
}
int
main
()
pthread_t
for
int
0
5
pthread_t
new
"thread-"
to_string
pthread_create
nullptr
push_back
for
int
0
5
pthread_join
nullptr
return
0

文章配图

通过上述代码创建多线程后,可验证每个线程是否有独立栈结构。在线程函数中定义局部变量 test,观察其地址和值。

void *threadRoutine(void *args) {
    int test = 1;
    threadData *td = (threadData *)args;
    int count = 5;
    while (count--) {
        printf("pid:%d, tid:%lx, threadname:%s, test:%d , &test:%p\n", getpid(), pthread_self(), td->threadname.c_str(), test, &test);
        test++;
        sleep(1);
    }
    return nullptr;
}

结果显示,每个线程中的局部变量 test 都是从 1 开始,且地址不同,证明每个线程拥有独立的线程栈,互不干扰。

对于全局数据,每个线程均可访问且共享同一地址。

int value = 10;
void *threadRoutine(void *args) {
    threadData *td = (threadData *)args;
    int count = 5;
    while (count--) {
        printf("pid:%d, tid:%lx, threadname:%s, value:%d , &value:%p\n", getpid(), pthread_self(), td->threadname.c_str(), value, &value);
        sleep(1);
    }
    return nullptr;
}

结果证实全局数据对每个线程共享。

线程局部存储 (TLS)

若需定义既是全局的又独属于线程的变量,可使用 __thread 关键字(GCC 提供的线程局部存储机制)。每个线程拥有独立的变量副本。

__thread int value = 10;

文章配图

加 __thread 后,每个线程拥有属于自己的全局变量副本,实现全局数据区的线程隔离。

线程分离

创建新线程后,主线程调用 pthread_join 会阻塞等待线程结束。若不关心退出结果,仅需线程完成任务后自动释放资源,可使用线程分离。

文章配图

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <string>
#include <vector>

struct threadData {
    std::string threadname;
};

__thread int value = 10;

void *threadRoutine(void *args) {
    pthread_detach(pthread_self());
    threadData *td = (threadData *)args;
    int count = 5;
    while (count--) {
        printf("pid:%d, tid:%lx, threadname:%s, value:%d , &value:%p\n", getpid(), pthread_self(), td->threadname.c_str(), value, &value);
        sleep(1);
    }
    return nullptr;
}

int main() {
    std::vector<pthread_t> tids;
    for (int i = 0; i < 3; i++) {
        pthread_t tid;
        threadData *td = new threadData;
        td->threadname = "thread-" + std::to_string(i);
        pthread_create(&tid, nullptr, threadRoutine, td);
        tids.push_back(tid);
    }
    // 主线程不再 join,直接结束
    return 0;
}

文章配图

线程分离后,主线程无需等待新线程。需注意主线程应在分离线程之后退出,否则程序终止会导致未完成的分离线程被强制回收。

线程的互斥

大部分情况下线程使用局部变量,但共享变量在多并发操作时可能引发问题。例如多线程抢票场景。

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <string>
#include <vector>

int tickets = 100;

class threadData {
public:
    threadData(int num) { threadname = "thread-" + std::to_string(num); }
public:
    std::string threadname;
};

void *getTicket(void *args) {
    threadData *td = (threadData *)args;
    while (1) {
        if (tickets > 0) {
            usleep(1000);
            printf("%s get a tickets , tickets : %d\n", td->threadname.c_str(), tickets);
            tickets--;
        } else {
            break;
        }
    }
    return nullptr;
}

int main() {
    std::vector<pthread_t> tids;
    std::vector<threadData *> thread_datas;
    for (int i = 1; i <= 3; i++) {
        pthread_t tid;
        threadData *td = new threadData(i);
        thread_datas.push_back(td);
        pthread_create(&tid, nullptr, getTicket, td);
        tids.push_back(tid);
    }
    for (int i = 0; i < 3; i++) {
        pthread_join(tids[i], nullptr);
    }
    for (auto thread : thread_datas) {
        delete thread;
    }
    return 0;
}

文章配图

可能出现票数变为负数或重复抢票的情况。这是因为 tickets-- 操作非原子性,包含读取、修改、写回三步。线程切换可能导致数据不一致。

解决此问题需保证临界区代码串行执行,即加锁。

互斥锁接口

  • pthread_mutex_init:初始化互斥锁。
  • pthread_mutex_lock:加锁,竞争失败则阻塞。
  • pthread_mutex_unlock:解锁,唤醒等待线程。
  • pthread_mutex_destroy:销毁互斥锁。

加锁示例

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <string>
#include <vector>

int tickets = 100;

class threadData {
public:
    threadData(int num, pthread_mutex_t *mutex) : mutex_(mutex) {
        threadname = "thread-" + std::to_string(num);
    }
public:
    std::string threadname;
    pthread_mutex_t *mutex_;
};

void *getTicket(void *args) {
    threadData *td = (threadData *)args;
    while (1) {
        pthread_mutex_lock(td->mutex_);
        if (tickets > 0) {
            usleep(1000);
            printf("%s get a tickets , tickets : %d\n", td->threadname.c_str(), tickets);
            tickets--;
        } else {
            pthread_mutex_unlock(td->mutex_);
            break;
        }
        pthread_mutex_unlock(td->mutex_);
    }
    return nullptr;
}

int main() {
    pthread_mutex_t mutex;
    pthread_mutex_init(&mutex, nullptr);
    std::vector<pthread_t> tids;
    std::vector<threadData *> thread_datas;
    for (int i = 1; i <= 3; i++) {
        pthread_t tid;
        threadData *td = new threadData(i, &mutex);
        thread_datas.push_back(td);
        pthread_create(&tid, nullptr, getTicket, td);
        tids.push_back(tid);
    }
    for (int i = 0; i < 3; i++) {
        pthread_join(tids[i], nullptr);
    }
    for (auto thread : thread_datas) {
        delete thread;
    }
    pthread_mutex_destroy(&mutex);
    return 0;
}

文章配图

加锁后解决了数据一致性问题,但可能导致饥饿现象(某线程持续获取锁)。这类似于多人争用单一电脑的场景,持有者释放锁后若立即再次获取,其他等待者将无法获得资源。

锁的原理

CPU 指令执行具有原子性。锁的实现常利用 CPU 提供的 swap 或 exchange 指令保证原子交换。

伪代码逻辑:

  1. 寄存器初始化为 0。
  2. 寄存器值与内存锁值原子交换。
  3. 判断寄存器值:大于 0 表示获取锁成功;否则挂起等待。

无论线程如何切换,原子交换确保同一时刻仅一个线程能获取锁。

线程的同步

线程同步要求在数据安全基础上保证访问顺序。条件变量可维护等待队列,避免忙等待。

当线程申请已被占用的锁时,调用条件变量等待接口进入挂起状态。锁释放后通知条件变量,唤醒一个等待线程重新获取锁。

条件变量接口

  • pthread_cond_init:初始化条件变量。
  • pthread_cond_wait:进入等待队列,挂起并释放互斥锁;唤醒后重新获取锁。
  • pthread_cond_signal:唤醒一个等待线程。
  • pthread_cond_broadcast:唤醒所有等待线程。
  • pthread_cond_destroy:销毁条件变量。

同步示例

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <string>
#include <vector>

int tickets = 100;

class threadData {
public:
    threadData(int num, pthread_mutex_t *mutex, pthread_cond_t *cond) : mutex_(mutex), cond_(cond) {
        threadname = "thread-" + std::to_string(num);
    }
public:
    std::string threadname;
    pthread_mutex_t *mutex_;
    pthread_cond_t *cond_;
};

void *getTicket(void *args) {
    pthread_detach(pthread_self());
    threadData *td = (threadData *)args;
    while (1) {
        pthread_mutex_lock(td->mutex_);
        pthread_cond_wait(td->cond_, td->mutex_);
        if (tickets > 0) {
            usleep(1000);
            printf("%s get a tickets , tickets : %d\n", td->threadname.c_str(), tickets);
            tickets--;
        } else {
            pthread_mutex_unlock(td->mutex_);
            break;
        }
        pthread_mutex_unlock(td->mutex_);
    }
    return nullptr;
}

int main() {
    pthread_mutex_t mutex;
    pthread_cond_t cond;
    pthread_mutex_init(&mutex, nullptr);
    pthread_cond_init(&cond, nullptr);
    std::vector<pthread_t> tids;
    std::vector<threadData *> thread_datas;
    for (int i = 1; i <= 3; i++) {
        pthread_t tid;
        threadData *td = new threadData(i, &mutex, &cond);
        thread_datas.push_back(td);
        pthread_create(&tid, nullptr, getTicket, td);
        tids.push_back(tid);
    }
    // 模拟信号发送
    while (tickets > 0) {
        pthread_cond_signal(&cond);
        sleep(1);
    }
    for (auto thread : thread_datas) {
        delete thread;
    }
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
    return 0;
}

文章配图

通过条件变量,所有线程均有机会抢到票,避免了单线程独占资源。注意条件变量的正确用法需结合生产者 - 消费者模型进一步探讨。

目录

  1. 线程栈
  2. 示例验证
  3. 线程局部存储 (TLS)
  4. 线程分离
  5. 线程的互斥
  6. 互斥锁接口
  7. 加锁示例
  8. 锁的原理
  9. 线程的同步
  10. 条件变量接口
  11. 同步示例

更多推荐文章

查看全部
  • AI 绘画精讲与 AIGC 时代游戏美术设计
  • AI 生成图片 R18 提示词:高效创作与合规性实践指南
  • Python 字典内部实现原理详解
  • 基于百川 2-13B 构建 AIGC 内容安全审核 Agent 实战
  • Python 环境搭建与安装配置完整指南
  • 2023 年高考大专就业前景较好的专业推荐
  • 基于 OpenClaw 与 Open WebUI 搭建企业多部门私有化 AI 平台
  • LeetCode 热题 100 Python3 算法题解:哈希、双指针与滑动窗口
  • Spring Boot 集成 WebSocket 实战:从原生到 STOMP 全解析
  • 基于 Python 的保险行业数据可视化分析系统
  • Edict:基于三省六部制的 AI Agent 协作框架
  • Web 自动化测试实战:Selenium 常用函数全解析与场景化应用
  • Java 消息队列选型总结:RabbitMQ、RocketMQ、Kafka 实战对比
  • GitHub Copilot 网络代理配置与故障排查指南
  • C++ 二叉搜索树(BST)核心实现与原理详解
  • 学生与教育工作者免费获取 GitHub Copilot 权限指南
  • Python 基础到进阶:从零开始系统掌握编程核心
  • 大模型开发生态总结与工业应用思考
  • Dify 工作流发布为 MCP Server 实战指南
  • 2024 数字安全十大技术趋势预测

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online