一、基础概念对比
特性 | 进程 (Process) | 线程 (Thread) |
---|---|---|
资源分配 | 资源分配的基本单位(独立地址空间) | 共享进程资源 |
调度单位 | 操作系统调度单位 | CPU调度的最小单位 |
创建开销 | 高(需复制父进程资源) | 低(共享进程资源) |
通信方式 | 管道、共享内存、消息队列等IPC | 共享全局变量(需同步机制) |
隔离性 | 内存隔离,安全性高 | 共享内存,需处理竞争条件 |
典型组成 | 代码段+数据段+堆栈段+PCB | 线程ID+寄存器组+栈+线程控制块TCB |
二、线程组成详解
1. 核心组件
struct thread_struct {
pthread_t tid; // 线程ID (8字节)
void* stack_base; // 栈基地址 (8字节)
size_t stack_size; // 栈大小 (Linux默认8MB)
void* (*start_routine)(void*); // 入口函数指针
void* arg; // 入口函数参数
// 寄存器组保存区 (约52个寄存器,约416字节)
// 包括:PC、SP、通用寄存器、浮点寄存器等
};
2. 关键特征
- 线程ID:
pthread_t
类型,进程内唯一 - 独立栈空间:每个线程拥有独立调用栈
- 共享资源:全局变量、堆内存、文件描述符等
三、线程创建与管理
1. 创建函数原型
#include <pthread.h>
int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine)(void *),
void *arg);
参数详解表
参数 | 类型 | 作用说明 |
---|---|---|
thread | pthread_t* | 输出参数,存储新线程ID |
attr | pthread_attr_t* | 线程属性(NULL使用默认属性):<br>▪ 栈大小<br>▪ 调度策略<br>▪ 分离状态 |
start_routine | void* (*)(void*) | 线程入口函数(返回值为线程退出状态) |
arg | void* | 传递给入口函数的参数 |
返回值
- 成功返回
0
- 失败返回错误码(非
errno
值,需用strerror
转换)
2. 编译指令
gcc program.c -lpthread -o program # 必须链接pthread库
四、线程生命周期管理
1. 线程终止方式
(1) 显式调用退出函数
void* worker(void* arg) {
int* heap_result = malloc(sizeof(int));
*heap_result = 100;
pthread_exit((void*)heap_result); // 正确方式:堆内存传递
// static int static_result = 200; // 替代方案:静态变量
// pthread_exit((void*)&static_result);
}
关键特性:
- 状态值通过
pthread_join()
获取 - 返回值必须使用堆内存或全局/静态变量
- 主线程调用时仅结束自身执行流
(2) 入口函数返回
void* worker(void* arg) {
static int result = 200; // 必须使用静态存储期变量
return (void*)&result; // 等效于pthread_exit()
}
禁止行为:
int local_var = 300;
return (void*)&local_var; // 错误!栈空间失效
(3) 被其他线程取消
// 取消请求端
pthread_cancel(target_tid);
// 被取消线程端
void* worker(void* arg) {
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
while(1) {
pthread_testcancel(); // 设置取消点
/* 长时间操作 */
}
}
取消类型对比:
类型 | 行为特征 | 设置函数 |
---|---|---|
PTHREAD_CANCEL_DEFERRED | 延迟到下一个取消点 | pthread_setcanceltype() |
PTHREAD_CANCEL_ASYNCHRONOUS | 立即终止(可能破坏数据) | pthread_setcanceltype() |
(4) 进程级终止
int main() {
pthread_t tid;
pthread_create(&tid, NULL, worker, NULL);
// return 0; // 错误!触发exit()终止所有线程
pthread_exit(NULL); // 正确:仅结束主线程
}
进程终止规则:
exit()
立即终止整个进程- 主线程
return
会隐式调用exit()
- 建议主线程始终使用
pthread_exit()
2. 状态回收机制
(1) 阻塞回收(Joinable模式)
void* status;
int ret = pthread_join(tid, &status);
if(ret == 0) {
printf("退出码: %d\n", *(int*)status);
free(status); // 必须释放堆内存
}
限制条件:
- 每个线程只能被join一次
- 已分离线程无法join
(2) 自动回收(Detached模式)
// 创建时设置分离属性
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, worker, NULL);
// 运行时分离
pthread_detach(existing_tid);
特性:
- 线程终止后自动回收资源
- 无法获取返回值
- 适用于后台任务线程
五、线程资源管理
1. 清理函数机制
void cleanup(void* arg) {
printf("清理资源: %p\n", arg);
free(arg);
}
void* worker(void* arg) {
void* res = malloc(1024);
pthread_cleanup_push(cleanup, res); // 注册清理函数
// 可能被取消的代码段
while(1) {
pthread_testcancel();
/* 临界操作 */
}
pthread_cleanup_pop(1); // 执行清理并出栈
return NULL;
}
触发条件:
pthread_cleanup_pop(非零值)
- 线程通过
pthread_exit()
退出 - 被其他线程取消
编码规范:
- push/pop必须成对出现
- 建议在可能被取消的代码段前注册
- 栈式管理(后进先出)
2. 属性管理
(1) 完整属性设置流程
pthread_attr_t attr;
pthread_attr_init(&attr); // 初始化
// 设置分离状态
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
// 设置栈大小(2MB示例)
size_t stack_size = 2 * 1024 * 1024;
void* stack_addr = malloc(stack_size);
pthread_attr_setstack(&attr, stack_addr, stack_size);
pthread_create(&tid, &attr, worker, NULL);
pthread_attr_destroy(&attr); // 销毁属性
(2) 常用属性API
函数 | 功能描述 |
---|---|
pthread_attr_setdetachstate | 设置分离/结合状态 |
pthread_attr_setstacksize | 设置线程栈大小 |
pthread_attr_setguardsize | 设置栈溢出保护区大小 |
pthread_attr_setschedpolicy | 设置调度策略(FIFO/RR等) |
六、线程同步机制
1. 互斥锁完整实现
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void* thread_func(void* arg) {
pthread_mutex_lock(&mutex);
/* 临界区操作 */
pthread_mutex_unlock(&mutex);
return NULL;
}
// 动态初始化方式
pthread_mutex_init(&mutex, NULL);
/* ... */
pthread_mutex_destroy(&mutex);
2. 同步机制对比
机制 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
互斥锁 | 共享资源访问控制 | 简单高效 | 可能产生死锁 |
读写锁 | 读多写少场景 | 提高读并发性能 | 写线程可能饿死 |
条件变量 | 线程间状态通知 | 精确唤醒机制 | 需配合互斥锁使用 |
信号量 | 资源数量控制 | 跨进程可用 | 功能相对基础 |
3. 全局变量竞争解决方案
pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;
int global_counter = 0;
void* counter_thread(void* arg) {
for(int i=0; i<100000; ++i) {
pthread_mutex_lock(&mutex);
global_counter++; // 原子操作
pthread_mutex_unlock(&mutex);
}
return NULL;
}
优化建议:
- 尽量减小临界区范围
- 避免嵌套锁
- 使用trylock避免死锁
七、高级主题与最佳实践
1. 返回值处理规范
(1) 简单状态码
// 传递整型值
pthread_exit((void*)(intptr_t)error_code);
// 接收端
int code = (int)(intptr_t)status;
(2) 复杂数据结构
struct Result {
int code;
char message[256];
};
void* worker(void* arg) {
struct Result* res = malloc(sizeof(struct Result));
/* 填充数据 */
pthread_exit(res);
}
// 接收端
struct Result* res = (struct Result*)status;
free(res);
2. 线程设计准则
-
资源管理三原则:
- 谁分配谁释放
- 退出前释放非共享资源
- 使用RAII模式管理资源
-
锁使用规范:
// 推荐加锁方式 pthread_mutex_lock(&mutex); do { /* 临界区操作 */ } while(0); pthread_mutex_unlock(&mutex); // 避免的写法 if(condition) pthread_mutex_unlock(&mutex); // 易漏解锁
-
错误处理模板:
int ret = pthread_create(&tid, NULL, worker, NULL); if(ret != 0) { fprintf(stderr, "线程创建失败: %s\n", strerror(ret)); exit(EXIT_FAILURE); }
3. 调试技巧
-
死锁检测:
- 使用
pthread_mutex_trylock()
测试锁状态 - 记录加锁顺序
- 使用Valgrind的Helgrind工具
- 使用
-
性能分析:
# 使用perf分析锁竞争 perf record -g -- ./program perf report
八、完整生命周期图示
graph TD
A[线程创建] --> B{执行阶段}
B -->|正常完成| C[资源回收]
B -->|被取消| D[清理处理]
C --> E[线程终止]
D --> E
E --> F[系统回收TID]
style B fill:#f9f,stroke:#333,stroke-width:2px
style C fill:#9f9,stroke:#333
style D fill:#f99,stroke:#333
subgraph 关键状态
B
C
D
end
九、常见问题解决方案
1. 僵尸线程问题
现象:线程终止但未回收,占用系统资源
解决方案:
// 方案1:及时join
void* retval;
pthread_join(tid, &retval);
free(retval);
// 方案2:设置分离属性
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, worker, NULL);
2. 返回值内存泄漏
错误示例:
void* worker() {
int result = 42;
pthread_exit(&result); // 栈内存泄露!
}
正确实践:
void* worker() {
int* result = malloc(sizeof(int)); // 堆内存
*result = 42;
pthread_exit(result);
}
3. 取消点设置不足
问题表现:取消请求长期不响应
优化方案:
void* worker(void* arg) {
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
while(1) {
pthread_testcancel(); // 每循环添加取消点
/* 长时间操作 */
}
return NULL;
}
十、扩展知识
1. 线程与进程对比
特性 | 进程 | 线程 |
---|---|---|
资源开销 | 高(独立地址空间) | 低(共享地址空间) |
通信方式 | 管道、共享内存、信号等 | 全局变量、互斥锁、条件变量 |
容错性 | 一个进程崩溃不影响其他 | 线程崩溃导致整个进程终止 |
上下文切换成本 | 高 | 低 |
2. 可重入函数设计
安全函数特征:
- 不使用静态变量
- 不调用非可重入函数
- 所有数据通过参数传递
示例对比:
// 不安全版本
char* strtok(char* str, const char* delim) {
static char* buffer; // 静态变量
/* ... */
}
// 可重入版本
char* strtok_r(char* str, const char* delim, char** saveptr) {
/* 使用传入的saveptr保存状态 */
}
十一、练习
练习1:创建一个线程
#include<stdio.h>
#include<pthread.h>
#include<errno.h>
#include<unistd.h>
void * do_something(void *arg)
{
printf("do copy file---\n");
return NULL;
}
int main(int argc, const char *argv[])
{
pthread_t tid;
int ret;
if((ret = pthread_create(&tid,NULL,do_something,NULL)) != 0)
{
errno = ret;
perror("pthread_create fail");
return -1;
}
printf("-----main-------\n");
sleep(1);
return 0;
return 0;
}
练习2:创建多个线程
#include<stdio.h>
#include<pthread.h>
#include<errno.h>
#include<unistd.h>
void * do_one(void *arg)
{
printf("pthread 1 pid = %d\n",getpid());
return NULL;
}
void * do_two(void *arg)
{
printf("pthread 2 pid = %d\n",getpid());
return NULL;
}
void * do_three(void *arg)
{
printf("pthread 3 pid = %d\n",getpid());
return NULL;
}
typedef void *(*thread_cb_t)(void*);
int main(int argc, const char *argv[])
{
printf("---main--- pid = %d\n",getpid());
pthread_t tid[3];
int ret;
thread_cb_t func[3] = {do_one,do_two,do_three};
int i = 0;
for(i = 0;i < 3;i++)
{
if((ret = pthread_create(&tid[i],NULL,func[i],NULL)) != 0)
{
errno = ret;
perror("pthread1_create fail");
return -1;
}
}
sleep(1);
return 0;
return 0;
}
练习3:线程的关闭
#include<stdio.h>
#include<pthread.h>
#include<errno.h>
#include<unistd.h>
void * do_something(void *arg)
{
static int ret = 100;
printf("do copy file---\n");
//pthread_exit("i am dead\n");
pthread_exit(&ret);
//return NULL;
}
int main(int argc, const char *argv[])
{
pthread_t tid;
int ret;
if((ret = pthread_create(&tid,NULL,do_something,NULL)) != 0)
{
errno = ret;
perror("pthread_create fail");
return -1;
}
printf("-----main-------\n");
int *retval;
//char *retval;
pthread_join(tid,(void **)&retval);
//printf("*retval = %s\n",retval);
printf("*retval = %d\n",*retval);
sleep(1);
return 0;
return 0;
}
练习4:多线程拷贝文件(缺陷当文件过大,会导致偏移量出错)
#include <stdio.h>
#include <pthread.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include <unistd.h>
typedef struct
{
int fd_s;
int fd_d;
int size;
int len;
int id;
}msg_t;
void * do_copy (void *arg)
{
msg_t p = *(msg_t*)arg;
lseek(p.fd_s,p.size*p.id,SEEK_SET);
lseek(p.fd_d,p.size*p.id,SEEK_SET);
// printf("tid = %ld id = %d fd_s = %d fd_d = %d size = %d len = %d\n",pthread_self(),p.id,p.fd_s,p.fd_d,p.size,p.len);//调试代码
char buf[p.len];
int ret = read(p.fd_s,buf,p.len);
write(p.fd_d,buf,ret);
return NULL;
}
//cp src dest
int main(int argc, const char *argv[])
{
if (argc!=3)
{
printf("Usage: %s <src> <dest>\n",argv[0]);
return -1;
}
int fd_s = open(argv[1],O_RDONLY);
int fd_d = open(argv[2],O_WRONLY|O_TRUNC|O_CREAT,0666);
if (fd_s < 0 || fd_d < 0)
{
perror("open fail");
return -1;
}
int n = 0;
printf("Input threads num: ");
scanf("%d",&n);
int i = 0;
int ret = 0;
pthread_t tid[n];
msg_t msg[n];
struct stat st;
if (stat(argv[1],&st) < 0)
{
perror("stat fail");
return -1;
}
int f_len = st.st_size;
for (i = 0; i < n; ++i)
{
msg[i].fd_s = fd_s;
msg[i].fd_d = fd_d;
msg[i].size = f_len / n;
msg[i].id = i;
#if 1
if (i == n-1)
{
msg[i].len = f_len - (f_len/n)*(n-1);
}else
{
msg[i].len = f_len/n;
}
#endif
ret = pthread_create(&tid[i],NULL,do_copy,&msg[i]);
if (ret != 0)
{
errno = ret;
perror("pthread_create fail");
return -1;
}
}
printf("----main-----\n");
for (i = 0; i < n; ++i)
pthread_join(tid[i],NULL);
close(fd_s);
close(fd_d);
return 0;
}
练习5、创建两个线程,线程1 打印 hello,线程2 打印 world,预期效果在屏幕上严格打印hello world
#include<stdio.h>
#include<pthread.h>
#include<errno.h>
#include<unistd.h>
pthread_mutex_t mutex;
int flag = 1;//为1时线程1执行,为2时线程2执行
void *do_something(void *arg)
{
while(1)
{
pthread_mutex_lock(&mutex);
if(flag ==1)
{
printf("Hello ");
flag = 2;//交换执行权限
}
pthread_mutex_unlock(&mutex);
usleep(10000);
}
return NULL;
}
void *do_something1(void *arg)
{
while(1)
{
pthread_mutex_lock(&mutex);
if(flag == 2)
{
printf("World!\n");
flag = 1;//交换执行权限
}
pthread_mutex_unlock(&mutex);
usleep(10000);
}
return NULL;
}
int main(int argc, const char *argv[])
{
pthread_t tid1, tid2;
int ret, ret1;
pthread_mutex_init(&mutex, NULL); // 初始化一把锁
if ((ret = pthread_create(&tid1, NULL, do_something, NULL)) != 0)
{
errno = ret;
perror("pthread_create for do_something fail");
return -1;
}
if ((ret1 = pthread_create(&tid2, NULL, do_something1, NULL)) != 0)
{
errno = ret1;
perror("pthread_create for do_something1 fail");
return -1;
}
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_mutex_destroy(&mutex);
return 0;
}