Linux线程编程入门与实践
一、线程基础概念
1. 什么是线程?
线程可以理解为"轻量级进程"(Lightweight Process),它是一个任务从创建、调度到消亡的完整过程。与进程类似,线程也有自己的执行流,但线程是操作系统任务调度的最小单元。
2. 进程与线程的区别
根据课程PDF中的总结,两者核心差异如下:
| 对比项 | 进程 | 线程 |
|---|---|---|
| 资源分配 | 操作系统资源分配的最小单元 | 共享进程资源,只有独立的栈空间 |
| 地址空间 | 独占0~4G虚拟内存(文本段、数据段、堆栈) | 位于进程内部,共享文本段、数据段、堆区,仅栈独立 |
| 创建开销 | 大(需复制资源) | 小(仅分配栈) |
| 通信方式 | 进程间通信(IPC)复杂 | 共享全局变量、堆区,通信简单 |
| 调度 | 宏观并行,微观串行 | 同样采用时间片轮转等调度算法 |
关键点:线程共享进程中的全局变量、静态变量、字符串常量、堆区,因此多线程间数据同步需特别小心(使用互斥锁等)。
二、线程相关函数接口(POSIX)
在Linux下使用pthread线程库,编译时需添加-lpthread链接选项。
1. 线程创建 ------ pthread_create
c
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine)(void *), void *arg);
- 功能:在进程中创建一个新线程。
- 参数 :
thread:用于存储线程ID(TID)的地址。attr:线程属性(栈大小、调度策略等),通常传NULL使用默认属性。start_routine:线程函数指针,返回void*,参数为void*。arg:传递给线程函数的参数。
- 返回值:成功返回0,失败返回错误码。
2. 线程结束 ------ pthread_exit
c
void pthread_exit(void *retval);
- 调用此函数会终止当前线程,并将
retval作为线程退出状态,供其他线程通过pthread_join获取。
3. 线程回收 ------ pthread_join
c
int pthread_join(pthread_t thread, void **retval);
- 功能:阻塞等待指定线程结束,并回收其资源,防止僵尸线程。
- 参数 :
thread为要等待的线程ID,retval用于存储线程退出状态(可为NULL)。 - 返回值:成功返回0,失败返回错误码。
注意:线程结束必须被回收,否则会产生僵尸线程,耗尽系统资源。
三、实践:多线程打印与动态控制
我们以课程中的作业为例,实现四个线程分别以不同间隔打印信息,主线程接收键盘字符来控制各线程的"打印/暂停"状态。
需求回顾
- 线程1(采集):每隔1秒打印"正在采集"
- 线程2(存储):每隔2秒打印"正在存储"
- 线程3(显示):每隔5秒打印"正在显示"
- 线程4(日志):每隔10秒打印"正在日志记录"
- 主进程接收
a/A切换采集线程的打印状态,b/B切换存储,c/C切换显示,d/D切换日志。
关键设计思路
- 线程永不退出 :使用
while(1)循环,内部通过标志位判断是否执行打印及休眠。 - 暂停时避免忙等 :当标志位为0时,调用
usleep(100000)(0.1秒)让出CPU,既省资源又能快速响应恢复指令。 - 主进程循环接收输入 :使用
getchar()持续读取,根据字符切换对应标志位。
核心代码示例
c
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
// 全局标志,volatile防止编译器过度优化
volatile int flag1 = 1, flag2 = 1, flag3 = 1, flag4 = 1;
void *caiji(void *arg) {
while (1) {
if (flag1) {
printf("采集线程(TID:%#lx) 正在采集\n", (unsigned long)pthread_self());
sleep(1);
} else {
usleep(100000); // 暂停0.1秒
}
}
return NULL;
}
// 其余线程函数类似,仅间隔和打印内容不同......
int main(void) {
pthread_t tid[4];
void *(*funcs[4])(void *) = {caiji, cunchu, xianshi, rizhi};
char ch;
for (int i = 0; i < 4; i++) {
pthread_create(&tid[i], NULL, funcs[i], NULL);
}
while (1) {
ch = getchar();
switch (ch) {
case 'a': case 'A': flag1 = !flag1; break;
case 'b': case 'B': flag2 = !flag2; break;
case 'c': case 'C': flag3 = !flag3; break;
case 'd': case 'D': flag4 = !flag4; break;
case 'q': case 'Q': // 退出程序
for (int i = 0; i < 4; i++) {
pthread_cancel(tid[i]); // 或使用退出标志
pthread_join(tid[i], NULL);
}
return 0;
default: break;
}
}
return 0;
}
编译运行
bash
gcc -o thread_demo thread_demo.c -lpthread
./thread_demo
按 a 可暂停/恢复采集线程的打印,按 q 退出。
四、注意事项与进阶
- 线程同步 :若多个线程访问共享标志,需使用互斥锁(
pthread_mutex_t)避免数据竞争。 - 线程属性 :可通过
pthread_attr_t设置线程分离状态(PTHREAD_CREATE_DETACHED),使线程结束后自动回收资源。 - 僵尸线程 :务必对每个创建的线程调用
pthread_join,或设为分离属性。 usleep的替代 :usleep在新标准中已被标记为废弃,可改用nanosleep或clock_nanosleep获得更精确的休眠。