跳到主要内容Linux 多线程编程:线程栈、TLS、互斥锁与条件变量详解 | 极客日志C++
Linux 多线程编程:线程栈、TLS、互斥锁与条件变量详解
综述由AI生成Linux 多线程编程涉及线程栈隔离、线程局部存储(TLS)、互斥锁及条件变量机制。线程栈为私有资源,全局数据共享但需保护。__thread 关键字实现线程私有全局变量。互斥锁解决临界区竞争问题,防止数据不一致。条件变量用于线程同步,避免忙等待,提高并发效率。通过抢票示例演示了竞态条件及加锁、同步解决方案。
筑梦师20 浏览 线程栈
在 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 后,每个线程拥有属于自己的全局变量副本,实现全局数据区的线程隔离。
线程分离
创建新线程后,主线程调用 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);
}
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 指令保证原子交换。
- 寄存器初始化为 0。
- 寄存器值与内存锁值原子交换。
- 判断寄存器值:大于 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;
}

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