Linux线程的共享资源与非共享资源详解
- [1. 线程基础概念回顾](#1. 线程基础概念回顾)
- [2. 线程共享资源](#2. 线程共享资源)
-
- [2.1 内存相关资源](#2.1 内存相关资源)
- [2.2 进程属性相关资源](#2.2 进程属性相关资源)
- [3. 线程非共享资源](#3. 线程非共享资源)
-
- [3.1 线程私有资源](#3.1 线程私有资源)
- [3.2 线程局部存储(TLS)](#3.2 线程局部存储(TLS))
- [4. 共享资源同步问题](#4. 共享资源同步问题)
-
- [4.1 典型问题:竞态条件](#4.1 典型问题:竞态条件)
- [4.2 同步解决方案](#4.2 同步解决方案)
- [5. 实际应用案例](#5. 实际应用案例)
-
- [5.1 Web服务器设计](#5.1 Web服务器设计)
- [5.2 生产者-消费者模型](#5.2 生产者-消费者模型)
- [6. 性能考量](#6. 性能考量)
-
- [6.1 共享资源访问开销](#6.1 共享资源访问开销)
- [6.2 优化建议](#6.2 优化建议)
- [7. 总结](#7. 总结)
1. 线程基础概念回顾
在Linux系统中,线程是程序执行的最小单位,也被称为轻量级进程(LWP)。与进程相比,线程创建、销毁和切换的开销更小,因为它们共享许多资源。Linux通过NPTL(Native POSIX Thread Library)实现线程,它使用clone()系统调用来创建线程。
进程 代码段 数据段 堆 打开的文件 信号处理器 线程1 线程2 线程ID 寄存器 栈 线程ID 寄存器 栈
2. 线程共享资源
2.1 内存相关资源
共享资源:
- 代码段:所有线程执行相同的程序代码
- 数据段:全局变量和静态变量
- 堆内存:动态分配的内存(malloc/new)
- 文件描述符表:打开的文件、套接字等
- 信号处理程序:信号处理器是进程级别的
c
#include <pthread.h>
#include <stdio.h>
int global_var = 0; // 共享变量
void* thread_func(void* arg) {
global_var++; // 所有线程共享访问
printf("Thread %ld: global_var = %d\n", (long)arg, global_var);
return NULL;
}
int main() {
pthread_t t1, t2;
pthread_create(&t1, NULL, thread_func, (void*)1);
pthread_create(&t2, NULL, thread_func, (void*)2);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("Final global_var = %d\n", global_var);
return 0;
}
2.2 进程属性相关资源
共享资源:
- 进程ID和父进程ID
- 进程组ID和会话ID
- 用户ID和组ID
- 当前工作目录
- 文件模式创建掩码(umask)
| 共享资源类别 | 具体内容 | 线程影响 |
|---|---|---|
| 内存资源 | 代码段、数据段、堆 | 线程可直接访问和修改 |
| 文件资源 | 文件描述符表 | 线程共享文件偏移量 |
| 进程属性 | 用户/组ID、工作目录 | 线程继承这些属性 |
3. 线程非共享资源
3.1 线程私有资源
非共享资源:
- 线程ID:每个线程有唯一标识符
- 寄存器状态:包括程序计数器PC
- 栈空间:每个线程有独立的调用栈
- 信号掩码:可以设置不同的阻塞信号
- 线程特定数据(TSD) :pthread_key_create创建
- errno变量:每个线程有自己的errno
Thread1 独立栈 Thread2 独立栈 Thread3 独立栈 共享堆
3.2 线程局部存储(TLS)
C11和GCC提供了_Thread_local关键字,POSIX有pthread_key_t:
c
#include <pthread.h>
#include <stdio.h>
__thread int tls_var = 0; // 每个线程有独立副本
void* thread_func(void* arg) {
tls_var += (long)arg;
printf("Thread %ld: tls_var = %d\n", (long)arg, tls_var);
return NULL;
}
int main() {
pthread_t t1, t2;
tls_var = 10;
pthread_create(&t1, NULL, thread_func, (void*)1);
pthread_create(&t2, NULL, thread_func, (void*)2);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("Main thread tls_var = %d\n", tls_var);
return 0;
}
4. 共享资源同步问题
4.1 典型问题:竞态条件
当多个线程同时访问共享资源且至少有一个线程修改资源时,如果没有正确同步,会导致不确定的结果。
案例:银行账户转账问题
c
int balance = 1000; // 共享账户余额
void* withdraw(void* arg) {
int amount = *(int*)arg;
if (balance >= amount) {
sleep(1); // 模拟处理延迟
balance -= amount;
}
return NULL;
}
// 两个线程同时取款800,可能导致余额为负数
4.2 同步解决方案
1. 互斥锁(Mutex)
c
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void* safe_withdraw(void* arg) {
int amount = *(int*)arg;
pthread_mutex_lock(&mutex);
if (balance >= amount) {
sleep(1);
balance -= amount;
}
pthread_mutex_unlock(&mutex);
return NULL;
}
2. 读写锁
c
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
// 读操作
pthread_rwlock_rdlock(&rwlock);
// 读取共享数据
pthread_rwlock_unlock(&rwlock);
// 写操作
pthread_rwlock_wrlock(&rwlock);
// 修改共享数据
pthread_rwlock_unlock(&rwlock);
5. 实际应用案例
5.1 Web服务器设计
典型的多线程Web服务器架构:
接受连接 接受连接 接受连接 主线程 工作线程1 工作线程2 工作线程3 共享资源: 缓存/会话数据
共享资源:
- 监听套接字(主线程accept后传递给工作线程)
- 内存缓存(如Redis数据)
- 会话状态
- 日志文件
非共享资源:
- 每个线程的处理套接字
- 线程本地缓冲区
- 请求处理状态
5.2 生产者-消费者模型
c
#define BUFFER_SIZE 10
int buffer[BUFFER_SIZE];
int count = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond_producer = PTHREAD_COND_INITIALIZER;
pthread_cond_t cond_consumer = PTHREAD_COND_INITIALIZER;
void* producer(void* arg) {
for (int i = 0; i < 20; i++) {
pthread_mutex_lock(&mutex);
while (count == BUFFER_SIZE) {
pthread_cond_wait(&cond_producer, &mutex);
}
buffer[count++] = i;
pthread_cond_signal(&cond_consumer);
pthread_mutex_unlock(&mutex);
}
return NULL;
}
void* consumer(void* arg) {
for (int i = 0; i < 20; i++) {
pthread_mutex_lock(&mutex);
while (count == 0) {
pthread_cond_wait(&cond_consumer, &mutex);
}
printf("Consumed: %d\n", buffer[--count]);
pthread_cond_signal(&cond_producer);
pthread_mutex_unlock(&mutex);
}
return NULL;
}
6. 性能考量
6.1 共享资源访问开销
| 操作类型 | 相对开销 | 说明 |
|---|---|---|
| 线程本地访问 | 1x | 访问栈变量最快 |
| 无竞争共享访问 | 1.2x | 访问全局变量稍慢 |
| 互斥锁保护访问 | 10-100x | 涉及系统调用和可能的上下文切换 |
| 条件变量等待 | 100-1000x | 线程挂起和唤醒开销大 |
6.2 优化建议
- 减少共享数据:尽可能使用线程局部存储
- 缩小临界区:只锁定必要的代码段
- 读写分离:使用读写锁替代互斥锁
- 无锁编程:在适当场景使用原子操作
- 线程局部缓存:减少对共享数据的直接访问
7. 总结
理解Linux线程的共享与非共享资源对于编写高效、安全的多线程程序至关重要。关键点总结:
- ✅ 共享资源:内存空间、文件描述符、进程属性等
- ❌ 非共享资源:栈、寄存器、线程特定数据等
- ⚠️ 同步必要:对共享资源的并发访问必须正确同步
- 🚀 性能优化:减少共享、缩小锁粒度、使用高效同步原语
通过合理设计线程间的资源共享与隔离,可以构建出既高效又可靠的多线程应用程序。