Linux----线程
一、基础概念对比
特性 | 进程 (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库
3. 线程终止方式
/* 主动退出(带返回值)*/
void pthread_exit(void *retval);
/* 被动终止(被其他线程取消)*/
int pthread_cancel(pthread_t thread);
注意事项
retval
必须指向堆/静态存储区,不能是线程栈内存- 主线程退出会导致进程终止(即使其他线程仍在运行)
四、线程终止方式详解(补充)
根据POSIX标准,线程可通过以下四种方式终止执行:
1. 显式调用退出函数
void* worker(void* arg) {
// 动态分配返回值
int* result = malloc(sizeof(int));
*result = 100;
// 显式退出并传递状态值
pthread_exit((void*)result); // 正确:堆内存
// pthread_exit(&local_var); // 危险!栈内存会被回收
}
特点:
- 退出状态值通过
pthread_join()
获取 - 必须保证返回值内存有效性(推荐使用堆内存或全局变量)
2. 从入口函数返回
void* worker(void* arg) {
static int result = 200; // 静态存储期变量
return (void*)&result; // 等效于 pthread_exit()
}
注意:
- 返回值类型必须为
void*
- 禁止返回局部变量地址(函数退出后栈空间失效)
3. 被其他线程取消
// 请求取消目标线程
pthread_cancel(tid);
// 目标线程中设置取消点
void* worker(void* arg) {
while(1) {
pthread_testcancel(); // 手动设置取消点
// 长时间工作...
}
return NULL;
}
关键机制:
取消类型 | 行为特征 | 设置函数 |
---|---|---|
PTHREAD_CANCEL_DEFERRED (默认) | 在下一个取消点终止 | pthread_setcanceltype() |
PTHREAD_CANCEL_ASYNCHRONOUS | 立即终止(可能破坏数据一致性) | pthread_setcanceltype() |
4. 进程级终止
void* thread_func(void* arg) {
sleep(1);
printf("此消息不会被打印\n");
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, thread_func, NULL);
// 错误示范:主线程直接返回
// return 0; // 导致所有线程立即终止
// 正确做法:主线程等待子线程
pthread_exit(NULL); // 仅退出主线程,不影响其他线程
}
重要规则:
exit()
会终止整个进程及其所有线程- 主线程
return
会隐式调用exit()
- 建议主线程使用
pthread_exit()
代替return
五、线程状态回收机制
1. 等待线程终止
void* status;
int ret = pthread_join(tid, &status);
if (ret == 0) {
printf("线程退出码:%d\n", *(int*)status);
free(status); // 清理堆内存
} else {
perror("等待线程失败");
}
2. 分离线程(自动回收)
// 创建时设置分离属性
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, worker, NULL);
// 或运行时分离
pthread_detach(tid);
//注意在建立线程后就设置分离
特性:
- 分离线程终止后自动回收资源
- 无法使用
pthread_join()
获取状态 - 适用于不需要返回值的后台任务
六、线程终止流程图解
graph TD
A[线程开始] --> B{终止方式}
B -->|pthread_exit| C[传递状态值]
B -->|return| C
B -->|pthread_cancel| D[清理处理程序]
B -->|exit| E[终止所有线程]
C --> F[状态值存储]
D --> G[调用清理栈函数]
F --> H[pthread_join获取]
G --> I[资源释放]
E --> J[进程终止]
style C fill:#c9f,stroke:#333
style D fill:#f96,stroke:#333
style E fill:#f00,stroke:#333
七、最佳实践建议
-
资源管理三原则:
- 谁分配谁释放
- 退出前释放非共享资源
- 使用
pthread_cleanup_push()
注册清理函数
-
取消安全设计:
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; }
-
状态值传递规范:
- 简单状态码使用
int
类型转换pthread_exit((void*)(intptr_t)error_code);
- 复杂数据结构使用堆内存
struct Result* res = malloc(sizeof(struct Result)); /* 填充数据 */ pthread_exit(res);
- 简单状态码使用
4.练习
练习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;
}
八、线程生命周期管理
1. 线程属性设置(示例)
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); // 分离属性
pthread_attr_setstacksize(&attr, 2*1024*1024); // 设置2MB栈
2. 线程同步机制
机制 | 用途 | 相关函数 |
---|---|---|
互斥锁 | 保护共享资源 | pthread_mutex_* 系列 |
条件变量 | 线程间事件通知 | pthread_cond_* 系列 |
读写锁 | 读写操作分离 | pthread_rwlock_* 系列 |
信号量 | 控制并发访问数量 | sem_* 系列 |
九、典型问题与解决方案
1. 资源竞争问题
场景:多个线程同时修改全局变量
解决:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void* counter_thread(void* arg) {
for(int i=0; i<100000; ++i) {
pthread_mutex_lock(&mutex);
global_counter++;
pthread_mutex_unlock(&mutex);
}
return NULL;
}
2. 僵尸线程问题
现象:已终止但未回收的线程占用系统资源
解决方案:
- 使用
pthread_join
阻塞回收:void* retval; pthread_join(tid, &retval); // 类似进程的waitpid free(retval); // 清理返回值
- 或设置分离属性:
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);