线程(pthread)知识点整理
1. 线程概念与特点
线程 vs 进程
| 特征 | 进程 | 线程 |
|---|---|---|
| 资源分配 | 最小资源分配单位 | 最小执行单位 |
| 资源共享 | 私有资源空间 | 共享进程资源,部分私有 |
| 通信方式 | IPC(复杂) | 直接通信(简单) |
| 创建开销 | 大 | 小(轻量级进程) |
| 稳定性 | 高 | 相对较低 |
| 效率 | 相对较低 | 高(节省30%资源) |
线程优点
-
资源共享:共享进程内的全局变量、堆区、文件描述符等
-
高效:创建销毁开销小,上下文切换快
-
并发性好:适合I/O密集型任务
-
简化通信:直接共享内存,无需复杂IPC
线程缺点
-
稳定性差:一个线程崩溃可能影响整个进程
-
调试复杂:gdb调试需要特殊命令
-
缺乏保护:线程间没有内存保护
2. POSIX线程编程框架
三部曲:创建线程 → 线程执行 → 资源回收
编译与头文件
#include <pthread.h> // 线程头文件
gcc -g -pthread source.c // 编译命令(-lpthread)
永久设置编译别名:
# 编辑 ~/.bashrc
alias gcc='gcc -g -pthread'
# 生效配置
source ~/.bashrc
3. 线程创建与管理
3.1 创建线程
int pthread_create(
pthread_t *thread, // 线程ID(出参)
const pthread_attr_t *attr, // 线程属性(NULL为默认)
void *(*start_routine)(void *), // 线程回调函数
void *arg // 回调函数参数
);
重要特性:
-
一次只能创建一个线程
-
主线程退出,所有子线程也退出
-
线程ID是CPU维护的唯一标识
-
多个线程可执行同一回调函数
3.2 获取线程ID
pthread_t pthread_self(void); // 获取当前线程ID
3.3 查看线程信息命令
ps -eLf # 查看所有线程
ps -eLo pid,ppid,lwp,stat,comm # 查看LWP(轻量级进程)
pstree # 查看线程树结构
4. 线程退出与回收
4.1 线程退出方式
// 1. 自行退出(自杀)
void pthread_exit(void *retval); // retval: 退出状态
// 2. 强制退出(他杀)
int pthread_cancel(pthread_t thread);
4.2 线程资源回收
int pthread_join(pthread_t thread, void **retval);
回收策略:
-
有限时间结束 →
pthread_join阻塞等待 -
可能休眠阻塞 → 超时后强制回收
-
长期运行 → 不回收(可能导致资源泄漏)
4.3 分离属性
// 方法1:设置属性
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, func, NULL);
pthread_attr_destroy(&attr);
// 方法2:直接分离
int pthread_detach(pthread_t thread);
5. 线程参数传递
5.1 传递整型参数
// 主线程
int value = 100;
pthread_create(&tid, NULL, func, (void *)(long)value);
// 子线程
void *func(void *arg) {
int val = (int)(long)arg;
// 使用val
return NULL;
}
5.2 传递字符串参数
// 栈区字符数组(危险:线程结束后可能失效)
char str[] = "hello";
pthread_create(&tid, NULL, func, str);
// 堆区字符串(安全)
char *str = malloc(128);
strcpy(str, "hello");
pthread_create(&tid, NULL, func, str);
// 线程结束后需要free
5.3 传递结构体参数
typedef struct {
int id;
char name[32];
float score;
} Student;
// 主线程
Student stu = {1, "Tom", 90.5};
pthread_create(&tid, NULL, func, &stu);
// 子线程
void *func(void *arg) {
Student *stu = (Student *)arg;
printf("ID:%d, Name:%s, Score:%.1f\n",
stu->id, stu->name, stu->score);
return NULL;
}
5.4 计算器示例
typedef struct {
float a;
float b;
char op; // + - * /
float result;
} Calculator;
void *calc_func(void *arg) {
Calculator *calc = (Calculator *)arg;
switch(calc->op) {
case '+': calc->result = calc->a + calc->b; break;
case '-': calc->result = calc->a - calc->b; break;
case '*': calc->result = calc->a * calc->b; break;
case '/':
if(calc->b != 0) calc->result = calc->a / calc->b;
else calc->result = 0;
break;
}
return NULL;
}
6. 线程返回值
返回值传递原理
-
pthread_exit(void *retval)→ 返回地址 -
pthread_join(tid, void **retval)→ 接收地址
有效返回地址类型:
-
全局变量(可直接访问,意义不大)
-
静态变量(生命周期长)
-
堆区变量(推荐:可控制生命周期)
// 示例:返回堆区字符串
void *thread_func(void *arg) {
char *result = malloc(128);
strcpy(result, "Thread completed successfully");
pthread_exit(result);
}
int main() {
pthread_t tid;
char *retval;
pthread_create(&tid, NULL, thread_func, NULL);
pthread_join(tid, (void **)&retval);
printf("Thread returned: %s\n", retval);
free(retval); // 必须释放
return 0;
}
7. 线程清理函数
void cleanup_func(void *arg) {
printf("Cleanup: %s\n", (char *)arg);
}
void *thread_func(void *arg) {
// 注册清理函数
pthread_cleanup_push(cleanup_func, "Resource cleanup");
// 线程工作代码
// do something...
// 执行清理函数(参数为0则不执行)
pthread_cleanup_pop(1); // 1:执行, 0:不执行
return NULL;
}
8. 线程互斥(Mutex)
互斥锁框架
// 1. 定义互斥锁
pthread_mutex_t mutex;
// 2. 初始化
pthread_mutex_init(&mutex, NULL);
// 3. 加锁
pthread_mutex_lock(&mutex);
// 临界区代码
pthread_mutex_unlock(&mutex); // 4. 解锁
// 5. 销毁
pthread_mutex_destroy(&mutex);
非阻塞加锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);
// 成功: 0, 失败: EBUSY(锁已被占用)
互斥锁示例:共享缓冲区
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
char buffer[1024];
pthread_mutex_t mutex;
void *writer_thread(void *arg) {
char *msg = (char *)arg;
for(int i = 0; i < 5; i++) {
pthread_mutex_lock(&mutex);
strcpy(buffer, msg);
printf("%s wrote: %s\n", msg, buffer);
pthread_mutex_unlock(&mutex);
usleep(100000); // 100ms
}
return NULL;
}
int main() {
pthread_t tid1, tid2;
pthread_mutex_init(&mutex, NULL);
pthread_create(&tid1, NULL, writer_thread, "Thread1");
pthread_create(&tid2, NULL, writer_thread, "Thread2");
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_mutex_destroy(&mutex);
return 0;
}
9. 线程同步(信号量)
信号量框架
#include <semaphore.h>
// 1. 定义信号量
sem_t sem;
// 2. 初始化(二值信号量)
sem_init(&sem, 0, 1); // 参数:信号量,pshared(0=线程), 初始值
// 3. P操作(申请资源)
sem_wait(&sem);
// 临界区
sem_post(&sem); // 4. V操作(释放资源)
// 5. 销毁
sem_destroy(&sem);
同步示例:生产者-消费者
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <string.h>
char buffer[256];
sem_t sem_empty; // 缓冲区空信号量
sem_t sem_full; // 缓冲区满信号量
void *input_thread(void *arg) {
while(1) {
sem_wait(&sem_empty); // 等待缓冲区空
printf("Enter message: ");
fgets(buffer, sizeof(buffer), stdin);
buffer[strlen(buffer)-1] = '\0'; // 去掉换行符
if(strcmp(buffer, "quit") == 0) {
sem_post(&sem_full);
break;
}
sem_post(&sem_full); // 通知处理线程
}
return NULL;
}
void *process_thread(void *arg) {
while(1) {
sem_wait(&sem_full); // 等待数据
if(strcmp(buffer, "quit") == 0) {
sem_post(&sem_empty);
break;
}
printf("Processed: Length=%lu\n", strlen(buffer));
sem_post(&sem_empty); // 通知可继续输入
}
return NULL;
}
int main() {
pthread_t tid1, tid2;
sem_init(&sem_empty, 0, 1); // 初始缓冲区为空
sem_init(&sem_full, 0, 0); // 初始无数据
pthread_create(&tid1, NULL, input_thread, NULL);
pthread_create(&tid2, NULL, process_thread, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
sem_destroy(&sem_empty);
sem_destroy(&sem_full);
return 0;
}
10. 综合示例:火车票售票系统
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#define TICKET_COUNT 100
int tickets = TICKET_COUNT;
pthread_mutex_t mutex;
int window1_count = 0;
int window2_count = 0;
void *sell_tickets(void *arg) {
char *window_name = (char *)arg;
while(1) {
pthread_mutex_lock(&mutex);
if(tickets > 0) {
printf("%s 卖出车票 %d\n", window_name, TICKET_COUNT - tickets + 1);
tickets--;
if(strcmp(window_name, "窗口1") == 0)
window1_count++;
else
window2_count++;
pthread_mutex_unlock(&mutex);
usleep(100000); // 模拟卖票时间
} else {
pthread_mutex_unlock(&mutex);
break;
}
}
return NULL;
}
int main() {
pthread_t tid1, tid2;
pthread_mutex_init(&mutex, NULL);
pthread_create(&tid1, NULL, sell_tickets, "窗口1");
pthread_create(&tid2, NULL, sell_tickets, "窗口2");
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
printf("\n售票统计:\n");
printf("窗口1卖出: %d张\n", window1_count);
printf("窗口2卖出: %d张\n", window2_count);
printf("总共卖出: %d张\n", TICKET_COUNT - tickets);
pthread_mutex_destroy(&mutex);
return 0;
}
11. 线程控制函数对比
| 进程操作 | 线程操作 | 功能 |
|---|---|---|
fork() |
pthread_create() |
创建 |
getpid() |
pthread_self() |
获取ID |
exit() |
pthread_exit() |
退出 |
wait() |
pthread_join() |
等待回收 |
kill() |
pthread_cancel() |
强制终止 |
atexit() |
pthread_cleanup_push/pop() |
清理函数 |
12. 死锁与预防
死锁产生条件
-
互斥条件:资源只能被一个线程使用
-
请求与保持:线程持有资源并请求新资源
-
不可剥夺:资源只能由持有者释放
-
循环等待:线程间形成等待环
预防策略
-
按顺序申请资源:所有线程按相同顺序申请锁
-
使用超时机制 :
pthread_mutex_trylock+ 超时重试 -
避免嵌套锁:尽量减少锁的嵌套层次
-
使用资源层级:为资源分配优先级
避免死锁示例
// 按固定顺序获取锁
void safe_operation(pthread_mutex_t *lock1, pthread_mutex_t *lock2) {
// 总是先获取lock1,再获取lock2
pthread_mutex_lock(lock1);
pthread_mutex_lock(lock2);
// 临界区操作
pthread_mutex_unlock(lock2);
pthread_mutex_unlock(lock1);
}
13. 线程调度与让出CPU
pthread_yield(); // 主动让出CPU(建议使用)
usleep(1000); // 休眠方式让出CPU
14. 综合练习:餐厅管理系统
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#define TABLE_COUNT 3
typedef struct {
char name[100];
int total_num;
int call_num;
pthread_mutex_t lock;
} Restaurant;
Restaurant rest;
sem_t tables; // 餐桌信号量
void *customer(void *arg) {
int id = *(int *)arg;
printf("顾客%d [%s] 正在用餐...\n", id, rest.name);
sleep(rand() % 3 + 1); // 用餐时间
printf("顾客%d [%s] 离开\n", id, rest.name);
sem_post(&tables); // 释放餐桌
free(arg);
return NULL;
}
void *waiter(void *arg) {
while(1) {
pthread_mutex_lock(&rest.lock);
if(rest.call_num < rest.total_num) {
sem_wait(&tables); // 等待空桌
int *id = malloc(sizeof(int));
*id = ++rest.call_num;
pthread_t tid;
pthread_create(&tid, NULL, customer, id);
pthread_detach(tid);
printf("服务员叫号: %d [%s]\n", rest.call_num, rest.name);
} else {
pthread_mutex_unlock(&rest.lock);
break;
}
pthread_mutex_unlock(&rest.lock);
sleep(1);
}
return NULL;
}
int main() {
pthread_t waiter_tid;
// 初始化
sem_init(&tables, 0, TABLE_COUNT);
pthread_mutex_init(&rest.lock, NULL);
rest.total_num = 0;
rest.call_num = 0;
// 输入顾客信息
printf("请输入顾客姓名: ");
fgets(rest.name, sizeof(rest.name), stdin);
rest.name[strlen(rest.name)-1] = '\0';
printf("请输入顾客总数: ");
scanf("%d", &rest.total_num);
getchar(); // 清除缓冲区
// 启动服务员线程
pthread_create(&waiter_tid, NULL, waiter, NULL);
pthread_join(waiter_tid, NULL);
// 清理
sem_destroy(&tables);
pthread_mutex_destroy(&rest.lock);
printf("所有顾客已服务完毕!\n");
return 0;
}
15. 线程调试技巧
GDB调试线程
gdb ./a.out
(gdb) run
(gdb) info threads # 查看所有线程
(gdb) thread 2 # 切换到线程2
(gdb) bt # 查看线程调用栈
(gdb) thread apply all bt # 查看所有线程调用栈
调试建议
-
编译带调试信息 :
gcc -g -pthread -
使用线程局部变量:避免全局变量冲突
-
添加调试输出:打印线程ID和状态
-
逐步测试:先测试单线程,再测试多线程
16.总结
多线程编程的关键在于正确处理共享资源 的同步 与互斥问题。合理使用互斥锁和信号量可以避免竞态条件和死锁,同时需要注意线程的生命周期管理和资源回收。在实际开发中,应根据具体需求选择合适的线程模型和同步机制。