Linux线程编程
一、线程核心理论基础
1. 线程是什么?
- 定义:线程是进程内的执行单元,也被称为"轻量级进程"(Lightweight Process),隶属于某个进程,共享进程的资源(代码段、数据段、文件描述符等)。
- 核心作用:实现并发执行,将耗时任务拆分到多个线程并行处理,提升程序效率(如视频渲染、网络请求并发处理)。
2. 线程的核心特征
| 特征 |
说明 |
| 资源分配 |
进程是系统最小资源分配单位,线程不单独分配资源(共享进程资源) |
| 执行单位 |
线程是系统最小执行单位,CPU调度的基本对象 |
| 层级关系 |
进程内线程平级,默认存在一个"主线程"(main函数所在线程) |
| 资源共享 |
线程间共享进程的全局变量、静态变量、文件描述符等,仅栈区(8MB)独立 |
| 稳定性 |
线程不稳定:一个线程崩溃会导致整个进程退出;进程相对稳定,互不影响 |
| 创建开销 |
线程创建仅需开辟独立栈区(8MB),进程创建需分配3GB虚拟地址空间,开销更大 |
| 并发度 |
线程并发度高于进程,同一进程内线程切换无需切换地址空间,效率更高 |
3. 线程与进程的核心区别
| 对比维度 |
线程(Thread) |
进程(Process) |
| 资源分配 |
共享所属进程资源,无独立地址空间 |
独立地址空间,资源独立(代码段、数据段等) |
| 创建/切换开销 |
小(仅开辟栈区) |
大(分配完整地址空间) |
| 通信方式 |
直接访问共享变量,通信简单 |
需通过IPC(管道、消息队列等),通信复杂 |
| 稳定性 |
低(线程崩溃导致进程退出) |
高(进程间相互隔离) |
| 并发效率 |
高(线程切换无需地址空间切换) |
低(进程切换开销大) |
4. 线程编程核心流程(POSIX标准)
- 创建多线程 :通过
pthread_create创建子线程,指定线程执行函数;
- 线程任务执行:子线程在回调函数中完成具体任务(资源操作、计算等);
- 线程资源回收 :通过
pthread_join(阻塞回收)或pthread_detach(自动回收)释放线程资源,避免内存泄漏。
5. 关键线程函数详解
POSIX线程函数库(libpthread)提供了线程操作的核心接口,以下是常用函数说明:
| 函数原型 |
功能描述 |
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg) |
创建子线程 - thread:输出参数,存储新线程ID - attr:线程属性(默认NULL) - start_routine:线程回调函数(执行入口) - arg:回调函数参数 - 返回值:成功0,失败返回错误码 |
pthread_t pthread_self(void) |
获取当前线程ID - 返回值:当前线程的ID(unsigned long类型,打印用%lu) |
void pthread_exit(void *retval) |
子线程主动退出 - retval:线程退出状态(返回给主线程) |
int pthread_cancel(pthread_t thread) |
主线程取消指定子线程 - thread:目标线程ID - 返回值:成功0,失败返回错误码 |
int pthread_join(pthread_t thread, void **retval) |
阻塞回收子线程资源 - thread:待回收线程ID - retval:接收子线程退出状态 - 返回值:成功0,失败返回错误码 |
int pthread_detach(pthread_t thread) |
设置线程分离属性(退出后自动回收资源) - 无需主线程调用pthread_join |
6. 线程查看命令
bash
复制代码
# 查看系统所有线程(PID:进程ID,LWP:线程ID,COMM:线程名称)
ps -eLo pid,ppid,lwp,stat,comm
# 查看线程详细信息(含CPU占用、内存等)
ps -eLf
二、实战代码解析(8个核心案例)
以下结合8个实战代码,从基础到进阶,逐步掌握线程编程技巧(所有代码需链接pthread库编译:gcc 文件名.c -o 文件名 -lpthread)。
案例01:创建多线程(01pthread.c)
功能:创建2个子线程,分别执行不同任务(发视频、接收控制)
c
复制代码
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
// 线程1回调函数:发视频
void *thread_function(void *arg)
{
while (1) {
printf("发视频...\n");
sleep(1); // 每隔1秒执行一次
}
return NULL;
}
// 线程2回调函数:接收控制
void *thread_function2(void *arg)
{
while (1) {
printf("接受控制...\n");
sleep(1);
}
return NULL;
}
int main()
{
pthread_t thread_id; // 线程1 ID
pthread_t thread_id2; // 线程2 ID
// 创建线程1
pthread_create(&thread_id, NULL, thread_function, NULL);
// 创建线程2
pthread_create(&thread_id2, NULL, thread_function2, NULL);
// 主线程阻塞(避免主线程退出导致子线程终止)
while (1) {
sleep(1);
}
return 0;
}
关键说明:
pthread_create参数:线程ID指针、默认属性(NULL)、回调函数、回调函数参数(NULL);
- 主线程需保持运行(
while(1)),否则主线程退出后,整个进程终止,子线程也会被销毁;
- 编译命令:
gcc 01pthread.c -o 01pthread -lpthread;
- 运行结果:两个子线程交替输出"发视频..."和"接受控制...",实现并发执行。
案例02:获取线程ID(02pthread_self.c)
功能:通过pthread_self()获取主线程和子线程的ID
c
复制代码
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
void *th1(void *arg)
{
while (1) {
// 打印子线程1 ID(%lu对应unsigned long类型)
printf("发视频...tid:%lu\n", pthread_self());
sleep(1);
}
return NULL;
}
void *th2(void *arg)
{
while (1) {
printf("接受控制...tid:%lu\n", pthread_self());
sleep(1);
}
return NULL;
}
int main()
{
pthread_t tid1, tid2;
pthread_create(&tid1, NULL, th1, NULL);
pthread_create(&tid2, NULL, th2, NULL);
// 打印主线程ID
while (1) {
printf("main tid:%lu\n", pthread_self());
sleep(1);
}
return 0;
}
关键说明:
pthread_self()无参数,返回当前线程的ID(类型为pthread_t,建议用%lu格式化输出);
- 运行结果:主线程和两个子线程分别输出各自的ID,可通过
ps -eLo lwp,comm验证线程是否存在。
案例03:线程退出(03pthread_exit.c)
功能:子线程通过pthread_exit()主动退出,主线程运行指定次数后退出
c
复制代码
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
void* th(void* arg){
while (1) {
printf("sub_th %lu\n", pthread_self());
sleep(1);
}
pthread_exit(NULL); // 子线程主动退出(此处因while(1)无法执行到,仅作演示)
}
int main(){
pthread_t tid;
pthread_create(&tid, NULL, th, NULL);
int i = 8;
// 主线程运行8秒后退出
while (i--) {
printf("main_th %lu\n", pthread_self());
sleep(1);
}
return 0;
}
关键说明:
pthread_exit(NULL):子线程主动退出,参数为退出状态(NULL表示无返回值);
- 注意:主线程退出后,子线程会被强制终止(即使子线程有
while(1));
- 运行结果:主线程输出8次后退出,子线程同时终止。
案例04:取消线程(04phread_cancel.c)
功能:主线程通过pthread_cancel()取消子线程
c
复制代码
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
void *thread_func(void *arg)
{
while (1) {
printf("subth %lu\n", pthread_self());
sleep(1);
}
}
int main()
{
pthread_t tid;
pthread_create(&tid, NULL, thread_func, NULL);
int i = 0;
while (1) {
printf("main th %lu\n", pthread_self());
sleep(1);
i++;
if (i == 2) {
// 运行2秒后,主线程取消子线程
pthread_cancel(tid);
printf("子线程已取消\n");
}
}
return 0;
}
关键说明:
pthread_cancel(tid):向指定子线程发送取消请求,子线程在"取消点"(如sleep、printf等系统调用)响应;
- 运行结果:子线程输出2次后被取消,主线程继续运行;
- 注意:
pthread_cancel仅发送请求,若子线程无取消点(如纯计算循环),需手动调用pthread_testcancel()设置取消点。
案例05:线程资源回收(05pthread_jion.c)
功能:主线程通过pthread_join()阻塞等待子线程完成,回收资源
c
复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
void* th(void* arg)
{
int i = 5;
while (i--) {
printf("workth,%lu\n", pthread_self());
sleep(1);
}
return NULL;
}
int main(int argc, char **argv)
{
pthread_t tid;
pthread_create(&tid, NULL, th, NULL);
// 阻塞等待子线程tid完成,回收其资源
pthread_join(tid, NULL);
printf("子线程已结束,主线程退出\n");
return 0;
}
关键说明:
pthread_join(tid, NULL):主线程阻塞,直到子线程tid退出,避免子线程成为"僵尸线程"(资源未回收);
- 第二个参数为
NULL,表示不关心子线程的退出状态;
- 运行结果:子线程输出5次后退出,主线程打印提示后退出。
案例06:获取线程返回值(06pthread_jionret.c)
功能:子线程动态分配内存并返回数据,主线程通过pthread_join()获取返回值并释放内存
c
复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
void* th(void* arg)
{
// 动态分配内存(子线程栈区数据不能返回,会随线程退出释放)
char* str = (char*)malloc(20);
strcpy(str, "我要灭亡了");
return str; // 返回动态内存地址
}
int main(int argc, char* argv[])
{
pthread_t tid;
pthread_create(&tid, NULL, th, NULL);
void* ret = NULL;
// 回收子线程,获取返回值(ret指向子线程分配的内存)
pthread_join(tid, &ret);
printf("子线程返回值:%s\n", (char*)ret);
free(ret); // 释放子线程分配的内存,避免内存泄漏
return 0;
}
关键说明:
- 子线程返回值不能是栈区变量(线程退出后栈区释放),需用
malloc动态分配内存;
- 主线程通过
pthread_join的第二个参数&ret接收返回值,使用后需手动free;
- 运行结果:主线程打印子线程返回的字符串"我要灭亡了"。
案例07:传递结构体参数(07.c)
功能:主线程向子线程传递结构体参数,子线程打印并返回结构体地址
c
复制代码
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
// 定义结构体(存储用户信息)
typedef struct
{
char name[20];
int age;
char address[50];
} PER;
void* th(void* arg)
{
// 将void*类型转换为结构体指针
PER* p = (PER*)arg;
printf("子线程接收的信息:\n");
printf("name:%s\n", p->name);
printf("age:%d\n", p->age);
printf("address:%s\n", p->address);