一、线程核心概论
1.1 基本定义
Linux 中,线程被称为 "轻量级进程(LWP,Light Weight Process)",线程属于进程------ 一个进程可以包含多个线程,所有线程共享进程的资源(如内存空间、文件描述符、信号处理等),同时拥有独立的执行流。
1.2 核心作用
与进程一致,线程的核心价值是实现并发执行,尤其适合处理:
- 耗时任务(如 IO 操作、网络通信);
- 并行计算(如多核心数据处理);
- 程序模块化拆分(如一个线程处理输入,一个线程处理输出)。
1.3 关键特征
| 维度 | 核心描述 |
|---|---|
| 资源分配 | 进程是系统最小的资源分配单位 (如内存、CPU 时间片);线程是系统最小的执行单位 |
| 层级关系 | 进程内的所有线程是平级关系,无父子之分;进程默认包含一个 "主线程"(main 函数对应的线程) |
| 资源范围 | 线程共享进程的全局资源(堆、全局变量、文件描述符),但拥有独立的栈区(默认 8MB) |
二、线程与进程的核心区别
| 特性 | 线程 | 进程 |
|---|---|---|
| 资源共享 | 共享进程资源(堆、全局变量等),仅栈区独立 | 资源完全独立(虚拟地址空间隔离) |
| 稳定性 | 一个线程崩溃会导致整个进程崩溃 | 单个进程崩溃不影响其他进程 |
| 创建开销 | 仅需开辟独立栈区(8MB),开销极低 | 需创建完整虚拟地址空间(3GB),开销大 |
| 并发度 | 高(切换成本低) | 低(切换需切换地址空间) |
| 通信方式 | 直接读写共享变量(需同步) | 需 IPC(管道、消息队列、共享内存等) |
三、线程编程核心步骤(POSIX 标准)
Linux 线程编程遵循 POSIX 标准(pthread 库),核心流程为:创建多线程 → 线程空间执行任务 → 线程资源回收(线程退出后栈区默认不释放,需手动 / 自动回收)
3.1 线程相关命令(调试 / 监控)
1. 查看线程信息
bash
运行
# 查看线程的PID、PPID、LWP(轻量级进程ID)、状态、命令
ps -eLo pid,ppid,lwp,stat,comm
# 更详细的线程信息(含线程ID、CPU占用等)
ps -eLf
2. 辅助命令(路径控制)
线程 / 进程的工作路径控制依赖以下函数(常与线程任务结合):
c
运行
// 获取当前工作路径
char *getcwd(char *buf, size_t size);
// 参数:buf-存储路径的数组;size-数组最大长度
// 返回值:成功返回buf指针,失败返回NULL
// 切换工作路径
int chdir(const char *path);
// 参数:path-目标路径(绝对/相对)
// 返回值:成功0,失败-1
3.2 线程核心函数(POSIX)
1. 创建线程:pthread_create
c
运行
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
- 功能 :创建一个新线程,线程启动后执行
start_routine函数; - 参数 :
thread:输出参数,存储新线程的 ID(需提前定义);attr:线程属性(如栈大小、分离属性),默认传NULL(使用默认属性);start_routine:线程的执行函数(回调函数),格式为void* 函数名(void*);arg:传给回调函数的参数(void * 类型,可传递任意数据);
- 返回值:成功返回 0,失败返回错误码(非 0)。
2. 获取当前线程 ID:pthread_self
c
运行
pthread_t pthread_self(void);
- 功能:获取当前线程的 ID;
- 返回值 :当前线程 ID(类型为
unsigned long int,打印用%lu)。
3. 线程退出:pthread_exit
c
运行
void pthread_exit(void *retval);
- 功能:线程主动退出(替代 return,更适合线程场景);
- 参数 :
retval:线程退出的返回值("临死遗言"),可被pthread_join获取; - 注意 :主线程调用
pthread_exit仅退出自身,不影响其他子线程。
4. 取消线程:pthread_cancel
c
运行
int pthread_cancel(pthread_t thread);
- 功能:主动请求终止指定线程;
- 参数 :
thread:要取消的线程 ID; - 返回值:成功 0,失败非 0;
- 注意:线程需处于 "可取消状态" 才会响应(默认可取消)。
5. 阻塞回收线程资源:pthread_join
c
运行
int pthread_join(pthread_t thread, void **retval);
- 功能:阻塞等待指定线程退出,并回收其资源(栈区);
- 参数 :
thread:要回收的线程 ID;retval:输出参数,存储线程的退出返回值(对应pthread_exit的参数);
- 返回值:成功 0,失败非 0;
- 核心特点:若线程未退出,调用者会阻塞,直到线程结束。
6. 设置线程分离属性:pthread_detach
c
运行
int pthread_detach(pthread_t thread);
- 功能:设置线程为 "分离属性",线程退出后资源(栈区)由系统自动回收;
- 参数 :
thread:要设置的线程 ID(可传pthread_self()设置自身); - 返回值:成功 0,失败非 0;
- 核心价值 :无需调用
pthread_join,避免僵尸线程(线程退出后资源未释放); - 注意 :设置分离属性后,无法再调用
pthread_join回收该线程。
四、线程编程实战示例
4.1 基础示例:创建线程 + 阻塞回收
c
运行
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
// 线程执行函数
void* thread_func(void* arg) {
char *msg = (char*)arg;
printf("子线程ID:%lu,参数:%s\n", pthread_self(), msg);
sleep(2);
// 线程退出,返回值为"thread exit"
pthread_exit((void*)"thread exit");
}
int main() {
pthread_t tid;
int ret;
void *retval;
// 创建线程
ret = pthread_create(&tid, NULL, thread_func, (void*)"hello thread");
if (ret != 0) {
perror("pthread_create failed");
exit(1);
}
printf("主线程ID:%lu,创建子线程ID:%lu\n", pthread_self(), tid);
// 阻塞回收线程,获取返回值
ret = pthread_join(tid, &retval);
if (ret != 0) {
perror("pthread_join failed");
exit(1);
}
printf("子线程退出,返回值:%s\n", (char*)retval);
return 0;
}
4.2 编译 & 运行
bash
运行
# 编译(必须链接pthread库)
gcc thread_demo.c -o thread_demo -lpthread
# 运行
./thread_demo
4.3 输出结果
plaintext
主线程ID:140709267896000,创建子线程ID:140709259507456
子线程ID:140709259507456,参数:hello thread
子线程退出,返回值:thread exit
4.4 分离属性示例
c
运行
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void* thread_func(void* arg) {
// 设置自身为分离属性
pthread_detach(pthread_self());
printf("子线程ID:%lu,执行任务\n", pthread_self());
sleep(2);
printf("子线程退出,资源自动回收\n");
pthread_exit(NULL);
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, thread_func, NULL);
// 主线程等待3秒,确保子线程执行完成
sleep(3);
printf("主线程退出\n");
return 0;
}
五、线程编程核心注意事项
- 编译必须链接 pthread 库 :pthread 是独立库,编译时需加
-lpthread,否则报 "undefined reference to pthread_create"; - 资源共享与同步:线程共享进程资源,多线程操作共享变量时需加互斥锁(pthread_mutex),避免数据竞争;
- 线程退出方式 :
- 子线程可通过
pthread_exit主动退出; - 主线程用
pthread_exit仅退出自身,用exit会终止整个进程;
- 子线程可通过
- 资源回收 :
- 非分离线程必须调用
pthread_join回收,否则会产生 "僵尸线程"; - 分离线程无需回收,系统自动释放资源;
- 非分离线程必须调用
- 线程安全 :避免在多线程中使用非线程安全函数(如
strtok),优先使用线程安全版本(如strtok_r)。