当前位置: 首页 > news >正文

Linux系统 -- 线程(pthread)核心知识整理

一、线程基本概念

1. 线程定义

线程是轻量级进程(LWP),是进程内部的多个并发执行任务,共享进程的大部分资源(如地址空间、文件描述符),仅拥有私有执行上下文(寄存器、栈、程序计数器)。

2. 线程与进程的核心区别

核心特性进程(Process)线程(Thread)
资源分配单位系统最小资源分配单位(独立地址空间、资源)系统最小执行单位(共享进程资源)
资源开销大(约 3GB 地址空间)小(约 8MB 线程栈)
数据共享方式需通过 IPC 机制(管道、共享内存等)除栈区外全部共享(全局变量、堆、文件描述符)
稳定性高(一个进程崩溃不影响其他进程)低(一个线程崩溃可能导致整个进程崩溃)
适用场景独立大任务(如浏览器、编辑器)同一任务的子任务(如视频发送 + 控制接收)
归属关系操作系统独立实体隶属于某个进程,依赖进程存在

共同点:均支持并发执行,由操作系统调度器分配 CPU 时间。

3. 线程的优缺点

优点缺点
比多进程节省系统资源(内存、CPU 切换开销)稳定性低(线程崩溃影响进程)
线程间数据共享便捷(无需 IPC)调试复杂(需 GDB 专用命令如 info thread

二、线程核心操作(POSIX 函数)

1. 线程创建:pthread_create

函数原型

c

#include <pthread.h>
int pthread_create(pthread_t *thread,           // 输出:存储新线程 ID 的指针const pthread_attr_t *attr,  // 输入:线程属性(NULL 表示默认属性)void *(*start_routine)(void *), // 输入:线程入口函数(函数指针)void *arg                    // 输入:传递给线程入口函数的参数(void* 类型)
);
功能与返回值
  • 功能:向内核请求创建新线程,绑定入口函数并传递参数。
  • 返回值:成功返回 0,失败返回非零错误码(如 EAGAIN 资源不足、EINVAL 属性无效)。
关键说明
  • 一次调用仅创建一个线程,每个进程至少有一个主线程(main 函数)。
  • 主线程退出(如 return/exit)会导致所有子线程强制退出(需用 pthread_exit 让主线程退出但保留子线程)。
  • 查看线程信息:ps -eLf 或 ps -eLo pid,ppid,lwp,stat,commlwp 为内核线程 ID)。
示例代码

c

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>// 线程1入口函数:发送视频
void *th1(void *arg) {while (1) {printf("th1 发送视频(tid:%lu)\n", pthread_self());sleep(1);}return NULL;
}// 线程2入口函数:接收控制
void *th2(void *arg) {while (1) {printf("th2 接收控制(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);while (1) sleep(1); // 主线程保持运行,避免子线程被终止return 0;
}

2. 获取线程 ID:pthread_self

函数原型

c

#include <pthread.h>
pthread_t pthread_self(void);
功能与返回值
  • 功能:获取当前线程的用户态 ID(pthread_t 类型)。
  • 返回值:当前线程的 ID(进程内唯一,跨进程不唯一)。
关键说明
  • 不可用 == 直接比较线程 ID(pthread_t 可能是结构体),需用 pthread_equal(tid1, tid2) 判断是否相等。
  • 与内核线程 ID 区别:pthread_self 返回用户态 ID,gettid() 系统调用返回内核态 TID(ps -L 中的 LWP)。
示例代码

c

// 线程函数中打印自身 ID
printf("th1 tid: %lu\n", pthread_self());
// 主线程打印自身 ID
printf("main tid: %lu\n", pthread_self());

3. 线程退出:两种方式

方式 1:自行退出(自杀)pthread_exit
函数原型

c

#include <pthread.h>
void pthread_exit(void *retval);
功能与参数
  • 功能:当前线程主动终止,可返回状态值(供 pthread_join 获取)。
  • 参数 retval:线程返回值(void* 类型,需指向有效内存,避免局部变量)。
关键说明
  • 在线程入口函数中,return retval 与 pthread_exit(retval) 效果一致。
  • 若在子函数中调用 pthread_exit,会直接终止整个线程(而非仅退出子函数)。
示例代码

c

void *th(void *arg) {int i = 3;while (i--) {printf("th 运行中(tid:%lu)\n", pthread_self());sleep(1);}pthread_exit(NULL); // 3 秒后自行退出,返回 NULL
}
方式 2:强制退出(他杀)pthread_cancel
函数原型

c

#include <pthread.h>
int pthread_cancel(pthread_t thread);
功能与参数
  • 功能:向指定线程发送 “取消请求”,线程在取消点(如 sleep/printf/read)响应并终止。
  • 参数 thread:要取消的线程 ID(由 pthread_create 输出)。
返回值
  • 成功返回 0,失败返回非零错误码(如 ESRCH 线程不存在)。
示例代码

c

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>void *th(void *arg) {while (1) {printf("th 运行中(tid:%lu)\n", pthread_self());sleep(1); // 取消点:线程会在此处检查取消请求}
}int main() {pthread_t tid;pthread_create(&tid, NULL, th, NULL);int i = 0;while (1) {printf("main 运行中(tid:%lu)\n", pthread_self());sleep(1);if (3 == i) { // 3 秒后强制取消子线程pthread_cancel(tid);printf("已发送取消请求\n");}i++;}return 0;
}

4. 线程回收:pthread_join

函数原型

c

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
功能与参数
  • 功能:阻塞主线程,等待指定线程结束,回收其内核资源(避免僵尸线程),并获取返回值。
  • 参数:
    • thread:要回收的线程 ID。
    • retval:输出:接收线程返回值的二级指针(无需获取返回值则设为 NULL)。
返回值
  • 成功返回 0,失败返回非零错误码(如 EINVAL 线程不可连接)。
关键说明
  • 仅能回收 “可连接线程”(默认状态),分离线程(pthread_detach)无法用 pthread_join 回收。
  • 线程返回值 *retval 需指向有效内存(堆 / 全局 / 静态变量),避免局部变量(线程结束后栈释放)。
示例代码

c

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>void *th(void *arg) {// 动态分配内存存储返回值(堆内存,线程结束后不释放)char *str = malloc(20);strcpy(str, "我要结束了");sleep(3); // 模拟任务执行pthread_exit(str); // 返回堆内存地址
}int main() {pthread_t tid;pthread_create(&tid, NULL, th, NULL);void *ret = NULL;pthread_join(tid, &ret); // 等待线程结束,获取返回值printf("线程返回值:%s\n", (char*)ret);free(ret); // 释放线程分配的堆内存return 0;
}

5. 线程分离属性:pthread_detach/ 属性初始化

核心作用

将线程设为 “分离状态”,线程结束后自动回收资源,无需 pthread_join(适用于无需获取返回值的线程,如日志线程)。

两种设置方式
方式 1:直接调用 pthread_detach

c

void *th(void *arg) {pthread_detach(pthread_self()); // 自身设为分离状态printf("分离线程运行中\n");sleep(2);return NULL; // 结束后自动回收资源
}int main() {pthread_t tid;pthread_create(&tid, NULL, th, NULL);sleep(3); // 等待线程结束(避免主线程先退出)return 0;
}
方式 2:通过线程属性初始化

c

#include <pthread.h>int main() {pthread_t tid;pthread_attr_t attr;pthread_attr_init(&attr); // 初始化属性pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); // 设置分离属性pthread_create(&tid, &attr, th, NULL); // 用分离属性创建线程pthread_attr_destroy(&attr); // 销毁属性(避免内存泄漏)sleep(3);return 0;
}
相关函数
  • pthread_attr_init(pthread_attr_t *attr):初始化线程属性。
  • pthread_attr_destroy(pthread_attr_t *attr):销毁线程属性,释放资源。

6. 线程清理函数:pthread_cleanup_push/pop

函数原型

c

#include <pthread.h>
void pthread_cleanup_push(void (*routine)(void *), void *arg); // 注册清理函数
void pthread_cleanup_pop(int execute);                        // 触发/取消清理函数
功能

在线程退出时(无论正常退出、pthread_cancel 或 pthread_exit),自动执行清理操作(如释放内存、关闭文件)。

参数说明
  • pthread_cleanup_push
    • routine:清理函数(参数为 void*,无返回值)。
    • arg:传递给清理函数的参数。
  • pthread_cleanup_pop
    • execute:非 0 表示执行清理函数,0 表示取消清理函数。
示例代码

c

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>// 清理函数:释放内存+关闭文件
typedef struct {FILE *fp;char *buf;
} CleanArgs;void clean(void *arg) {CleanArgs *args = (CleanArgs*)arg;printf("执行清理操作\n");free(args->buf);   // 释放堆内存fclose(args->fp);  // 关闭文件
}void *th(void *arg) {FILE *fp = fopen("test.txt", "w");char *buf = malloc(20);CleanArgs args = {fp, buf};pthread_cleanup_push(clean, &args); // 注册清理函数fputs("hello thread", fp); // 业务逻辑strcpy(buf, "test");pthread_cleanup_pop(1); // 执行清理函数(1 表示触发)return NULL;
}int main() {pthread_t tid;pthread_create(&tid, NULL, th, NULL);pthread_join(tid, NULL);return 0;
}

三、线程参数与返回值传递

1. 基本参数传递(整数、字符串)

传递整数

c

void *th(void *arg) {int num = *(int*)arg; // 转换为 int* 并解引用printf("线程接收的整数:%d\n", num);return NULL;
}int main() {pthread_t tid;int num = 100;pthread_create(&tid, NULL, th, &num); // 传递整数地址pthread_join(tid, NULL);return 0;
}
传递字符串
字符串类型特点推荐度
栈区字符数组线程结束后内存释放,易悬空❌ 不推荐
字符串常量("abc"全局有效但不可修改⚠️ 谨慎使用
堆区字符串(malloc可修改、生命周期可控✅ 推荐

示例(堆区字符串):

c

void *th(void *arg) {char *str = (char*)arg;strcat(str, " world"); // 修改堆区字符串return str;
}int main() {pthread_t tid;char *str = malloc(20);strcpy(str, "hello");pthread_create(&tid, NULL, th, str); // 传递堆区字符串地址void *ret = NULL;pthread_join(tid, &ret);printf("线程返回的字符串:%s\n", (char*)ret); // 输出 "hello world"free(ret); // 释放堆内存return 0;
}

2. 结构体参数传递(复杂数据)

示例代码

c

#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>// 定义结构体:存储个人信息
typedef struct {char name[50];  // 姓名int age;        // 年龄char addr[100]; // 地址
} PER;// 线程函数:给结构体赋值
void *th(void *arg) {PER *per = (PER*)arg; // 转换为结构体指针// 读取姓名(去除换行符)printf("输入姓名:");fgets(per->name, sizeof(per->name), stdin);per->name[strlen(per->name)-1] = '\0';// 读取年龄(字符串转整数)printf("输入年龄:");char tmp[5] = {0};fgets(tmp, sizeof(tmp), stdin);per->age = atoi(tmp);// 读取地址printf("输入地址:");fgets(per->addr, sizeof(per->addr), stdin);return per; // 返回结构体指针
}int main() {PER per; // 主线程栈上创建结构体pthread_t tid;pthread_create(&tid, NULL, th, &per); // 传递结构体地址void *ret = NULL;pthread_join(tid, &ret);// 打印结构体数据printf("\n姓名:%s\n年龄:%d\n地址:%s\n", ((PER*)ret)->name, ((PER*)ret)->age, ((PER*)ret)->addr);return 0;
}
关键说明
  • 结构体存储在主线程栈上:线程仅修改结构体内容,不 ownership,无需释放。
  • 若结构体在堆上(malloc):主线程需在回收线程后 free 结构体。

四、线程与进程函数对比

进程操作函数对应的线程操作函数功能说明
fork()pthread_create()创建新执行单元
getpid()/getppid()pthread_self()获取自身 ID
exit()pthread_exit()终止自身
wait()/waitpid()pthread_join()等待并回收执行单元资源
kill()pthread_cancel()强制终止指定执行单元
atexit()pthread_cleanup_push()注册退出时的清理函数
exec() 系列无(线程共享地址空间,不支持)替换进程地址空间(线程不适用)

五、线程核心要点回顾

  1. 本质:轻量级进程,共享进程资源,仅私有栈和执行上下文。
  2. 核心优势:资源开销小、数据共享便捷,适合并发子任务。
  3. 关键函数
    • 创建:pthread_create
    • 回收:pthread_join
    • 退出:pthread_exit/pthread_cancel
    • 分离:pthread_detach
  4. 内存安全
    • 参数 / 返回值需指向有效内存(堆 / 全局 / 静态变量),避免局部变量。
    • 共享数据需同步(后续需学习互斥锁、条件变量)。
  5. 调试技巧:GDB 中用 info thread 查看线程列表,thread <ID> 切换线程。

http://www.dtcms.com/a/354264.html

相关文章:

  • 【golang长途旅行第33站】常量------补充知识点
  • 学习游戏制作记录(数据加密以及主菜单和画面优化)8.27
  • 运算电源抑制比(PSRR)测量及设计注意事项
  • 去哪里学AI?2025年AI培训机构推荐!
  • 部署k8s-efk日志收集服务(小白的“升级打怪”成长之路)
  • 数据库:缓冲池和磁盘I/O
  • 让组件“活”起来:使用 `useState` Hook 管理组件状态
  • 【苍穹外卖项目】Day12
  • Android中的SELinux
  • vue3 字符 居中显示
  • HyperMesh许可证过期?
  • 北京国标:专业高效的数据采集和分析服务
  • 【深入理解 Linux 网络】配置调优与性能优化
  • 官宣,2026第二届郑州国际台球产业展览会,展位开启招商
  • 解决网站图片加载慢:从架构原理到实践
  • Ubuntu系统中查看内存、CPU、GPU的使用情况以及它们之间的连接情况
  • TypeScript实战:轻松实现数字序号转中文大写数字
  • 什么是宏观和微观仿真
  • Wed 自动化测试常用函数实践(二)
  • 嵌入式开发学习 C++:day01
  • 【SystemUI】启动屏幕录制会自动开启投屏
  • 主流配置中心对比
  • 百度测试岗位--面试真题分析
  • JS逆向-反调试绕过事件检测无限Debug篡改猴Hook替换指向匹配修改条件断点
  • 泊松分布知识点讲解
  • Android WPS Office 18.20
  • 【Win软件 - 系统 - 网络】 Windows怎么禁止某个应用联网
  • 洛谷P13849 [CERC 2023] Equal Schedules题解
  • python接口自动化测试报告插件使用
  • CSS扩大点击热区示例