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

Linux 多线程编程完全指南(上):线程创建、退出与同步

引言

在之前的文章中,我们学习了进程间通信(IPC)的各种机制。今天,我们将进入一个全新的主题——多线程编程

线程是进程内部的一条执行路径。简单来说:

  • 单线程程序:只有主线程(main函数)一条执行路径

  • 多线程程序:一个进程内存在多条并发执行的路径,可以同时执行多个任务

线程与普通函数的区别:

  • 普通函数:调用后等待返回,执行顺序是串行的

  • 线程函数:启动后独立执行,与主线程并发运行,无需等待返回

今天,我将从基础开始,全面讲解Linux多线程编程的核心概念、创建方法、退出与等待机制,以及常见的参数传递问题。

第一部分:线程基础

一、线程的基本概念

线程是进程内部的执行单元,同一进程内的多个线程共享进程的资源(内存、文件描述符等)。

特性进程线程
资源独立的内存空间共享进程的内存空间
创建开销
切换开销
通信方式IPC(管道、共享内存等)直接访问共享变量
同步机制信号量、互斥锁互斥锁、条件变量

二、线程库头文件与编译

多线程编程需要包含头文件<pthread.h>,编译时需要链接pthread库。

#include <pthread.h> #include <stdio.h> #include <unistd.h> // 编译命令 // gcc main.c -o main -lpthread

三、核心函数

函数作用原型
pthread_create创建新线程int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);
pthread_exit退出当前线程void pthread_exit(void *retval);
pthread_join等待线程结束int pthread_join(pthread_t thread, void **retval);

第二部分:创建线程(pthread_create)

一、函数参数详解

int pthread_create(pthread_t *thread, // 输出:线程ID const pthread_attr_t *attr, // 线程属性(NULL表示默认) void *(*start_routine)(void*), // 线程函数 void *arg); // 传递给线程函数的参数

参数说明:

  • thread:输出参数,创建成功后存储线程ID

  • attr:线程属性,通常设为NULL使用默认属性

  • start_routine:线程函数,必须是void* func(void*)类型

  • arg:传递给线程函数的参数,没有则传NULL

二、基本示例:主线程与子线程并发

#include <stdio.h> #include <unistd.h> #include <pthread.h> void* func(void* arg) { for (int i = 0; i < 5; i++) { printf("子线程执行中\n"); sleep(1); } return NULL; } int main() { pthread_t tid; // 创建子线程 pthread_create(&tid, NULL, func, NULL); // 主线程也执行5次 for (int i = 0; i < 5; i++) { printf("主线程执行中\n"); sleep(1); } return 0; }

执行效果:

主线程执行中
子线程执行中
主线程执行中
子线程执行中
...

主线程和子线程并发执行,输出交替出现。

三、线程ID的查看

#include <stdio.h> #include <unistd.h> #include <pthread.h> void* func(void* arg) { printf("子线程ID: %lu\n", pthread_self()); return NULL; } int main() { pthread_t tid; printf("主线程ID: %lu\n", pthread_self()); pthread_create(&tid, NULL, func, NULL); sleep(1); // 等待子线程执行 return 0; }

第三部分:线程退出与等待

一、pthread_exit——退出当前线程

pthread_exit只终止当前线程,不影响其他线程;而exit()会终止整个进程。

#include <stdio.h> #include <unistd.h> #include <pthread.h> void* func(void* arg) { for (int i = 0; i < 5; i++) { printf("子线程执行中: %d\n", i); if (i == 2) { pthread_exit(NULL); // 子线程提前退出 } sleep(1); } return NULL; } int main() { pthread_t tid; pthread_create(&tid, NULL, func, NULL); for (int i = 0; i < 5; i++) { printf("主线程执行中: %d\n", i); sleep(1); } return 0; }

重要区别:

函数作用影响
pthread_exit()退出当前线程其他线程继续运行
exit()退出整个进程所有线程都被终止

二、pthread_join——等待线程结束

pthread_join使主线程阻塞,等待指定的子线程结束。

#include <stdio.h> #include <unistd.h> #include <pthread.h> void* func(void* arg) { for (int i = 0; i < 5; i++) { printf("子线程执行中: %d\n", i); sleep(1); } return NULL; } int main() { pthread_t tid; pthread_create(&tid, NULL, func, NULL); printf("主线程等待子线程结束...\n"); pthread_join(tid, NULL); // 阻塞等待 printf("子线程已结束,主线程继续\n"); return 0; }

执行结果:

主线程等待子线程结束... 子线程执行中: 0 子线程执行中: 1 子线程执行中: 2 子线程执行中: 3 子线程执行中: 4 子线程已结束,主线程继续

三、获取线程返回值

线程可以通过pthread_exit返回数据,主线程通过pthread_join接收。

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> void* func(void* arg) { int* result = (int*)malloc(sizeof(int)); *result = 42; printf("子线程准备返回\n"); pthread_exit((void*)result); // 返回数据 } int main() { pthread_t tid; void* retval; pthread_create(&tid, NULL, func, NULL); pthread_join(tid, &retval); // 接收返回值 printf("子线程返回值: %d\n", *(int*)retval); free(retval); // 记得释放内存 return 0; }

重要注意事项:

  • 不能返回局部变量的地址(函数结束后局部变量被销毁)

  • ✅ 返回全局变量、静态变量、动态分配的内存(需手动释放)

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> void* func(void* arg) { int* result = (int*)malloc(sizeof(int)); *result = 42; printf("子线程准备返回\n"); pthread_exit((void*)result); // 返回数据 } int main() { pthread_t tid; void* retval; pthread_create(&tid, NULL, func, NULL); pthread_join(tid, &retval); // 接收返回值 printf("子线程返回值: %d\n", *(int*)retval); free(retval); // 记得释放内存 return 0; }

第四部分:创建多个线程

一、创建多个线程

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> #define THREAD_NUM 5 void* thread_func(void* arg) { int num = *(int*)arg; printf("线程 %d 开始执行\n", num); sleep(1); printf("线程 %d 结束执行\n", num); return NULL; } int main() { pthread_t tids[THREAD_NUM]; for (int i = 0; i < THREAD_NUM; i++) { // 注意:直接传递 i 的地址会有问题! pthread_create(&tids[i], NULL, thread_func, &i); } for (int i = 0; i < THREAD_NUM; i++) { pthread_join(tids[i], NULL); } return 0; }

二、地址传参的问题与解决方案

问题现象

上面的代码存在一个经典问题:所有线程可能输出相同的值,或者输出顺序和创建顺序不一致。

原因分析:

  • 线程创建后不一定立即执行

  • 当线程真正执行时,主线程可能已经修改了循环变量i的值

  • 多个线程同时读取主线程的变量,造成数据竞争

// 常见错误输出:
线程 5 开始执行
线程 5 开始执行
线程 5 开始执行
线程 5 开始执行
线程 5 开始执行

解决方案:动态内存分配

为每个线程创建独立的数据副本,避免共享变量:

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> #define THREAD_NUM 5 void* thread_func(void* arg) { int num = *(int*)arg; free(arg); // 使用后释放内存 printf("线程 %d 开始执行\n", num); sleep(1); printf("线程 %d 结束执行\n", num); return NULL; } int main() { pthread_t tids[THREAD_NUM]; for (int i = 0; i < THREAD_NUM; i++) { // 为每个线程分配独立的内存空间 int* p = (int*)malloc(sizeof(int)); *p = i; pthread_create(&tids[i], NULL, thread_func, p); } for (int i = 0; i < THREAD_NUM; i++) { pthread_join(tids[i], NULL); } return 0; }

关键点:

  • 每次循环都分配独立的内存空间

  • 每个线程访问自己的数据副本,互不干扰

  • 线程使用完后需要释放内存,防止内存泄漏


第五部分:并发与并行的概念

一、并发(Concurrency)

并发是指单个处理器通过时间片轮转机制交替执行多条路径。在任意某一时刻,仅有一条路径处于执行状态。

单核CPU并发执行: 时间 → 线程A: ■□■□■□■□■□ 线程B: □■□■□■□■□■ ↑ ↑ 交替执行,某一时刻只有一个在运行

二、并行(Parallelism)

并行是指多个处理器同时执行多条路径。在任意某一时刻,多条路径均处于执行状态。

多核CPU并行执行: 核心1: ■■■■■■■■■■ 核心2: ■■■■■■■■■■ ↑ ↑ 同时执行,多路径同时运行
维度并发并行
硬件要求单处理器即可需要多处理器
执行特征时间片轮转交替执行多路径同时执行
观测表现某一时刻仅单路径活跃某一时刻多路径均活跃
关系并行是并发的特殊形式-

三、线程执行的不确定性

由于并发调度的不确定性,线程的执行顺序无法预知:

#include <stdio.h> #include <pthread.h> #define THREAD_NUM 5 void* thread_func(void* arg) { int num = *(int*)arg; free(arg); printf("%d ", num); return NULL; } int main() { pthread_t tids[THREAD_NUM]; for (int i = 0; i < THREAD_NUM; i++) { int* p = (int*)malloc(sizeof(int)); *p = i; pthread_create(&tids[i], NULL, thread_func, p); } for (int i = 0; i < THREAD_NUM; i++) { pthread_join(tids[i], NULL); } printf("\n"); return 0; }

可能的输出:

0 1 2 3 4 或 4 0 1 2 3 或 1 3 0 2 4 等等(随机顺序)

原因:线程调度由操作系统决定,受处理器空闲状态、调度策略、系统负载等多种因素影响。


总结

一、线程核心函数速查表

函数原型说明
pthread_createint pthread_create(pthread_t*, const pthread_attr_t*, void*(*)(void*), void*)创建线程
pthread_exitvoid pthread_exit(void*)退出当前线程
pthread_joinint pthread_join(pthread_t, void**)等待线程结束
pthread_selfpthread_t pthread_self(void)获取当前线程ID

二、线程参数传递的注意事项

方式安全性说明
直接传值✅ 安全适合小数据(需强制类型转换)
传地址(局部变量)❌ 危险线程执行时变量可能已被销毁
传地址(全局/静态变量)⚠️ 谨慎多个线程共享,可能被修改
动态分配内存✅ 安全每个线程独立副本,需手动释放

三、线程创建与等待流程图

四、编译命令

# 必须链接pthread库 gcc main.c -o main -lpthread # 新版系统可能自动链接,但建议显式声明以保证兼容性

本篇文章介绍了线程的基本概念、创建、退出和等待机制,以及多线程编程中的常见问题。

核心要点回顾:

  1. 线程是进程内部的执行路径,多个线程并发执行

  2. pthread_create创建线程,pthread_join等待线程结束

  3. 线程返回值不能是局部变量地址

  4. 创建多个线程时,参数传递需要特别注意数据隔离

  5. 线程执行顺序由系统调度决定,具有不确定性

http://www.jsqmd.com/news/719772/

相关文章:

  • 告别Altova XMLSpy,用VSCode插件高效编写EtherCAT从站ESI文件(附配置模板)
  • 大文件上传卡顿、OOM崩溃、超时失败,PHP 8.9分块处理到底缺哪一环?
  • 避坑指南:FPGA读写SPI Flash(S25FL系列)时,为什么你的擦除和写入总失败?
  • 2026年全国消防排烟风机与工业通风源头厂家完全采购指南 - 优质企业观察收录
  • Int J Surg(IF=10.1)南方医科大学珠江医院放射科全显跃等团队:CT在线计算器预测肝细胞癌术后预后及PA-TACE获益:开发与验证
  • 2026最新保密印刷企业厂家推荐!云南权威榜单发布,质效双优昆明印刷制造商推荐 - 十大品牌榜
  • 脸黄长斑不用慌,无极秀美白淡斑面霜温和补水提亮焕白肌肤 - 资讯焦点
  • caj2pdf终极指南:将CAJ文献转换为可编辑PDF的完整教程
  • RTX 4090D 24G镜像效果展示:CogVideoX视频生成模型长时序连贯性实测
  • 2026 AI文献代查工具权威评测|6 款工具实测,这款AI专题文献代查成为科研必备 - 逢君学术-AI论文写作
  • 2026最新书刊印刷企业推荐!云南优质印刷服务商榜单发布,实力靠谱昆明印刷供应商首选 - 十大品牌榜
  • 深度解析:基于国产化异构计算的 AI 视频管理平台架构——从 GB28181 接入到 NPU 边缘推流的解耦实践
  • 公考小白如何迈出第一步?上岸村等机构的“零基础入门”课程模式解析 - 资讯焦点
  • 南昌地道火锅门店实测:热辣宗师8家直营点全解析 - 资讯焦点
  • Scroll Reverser:macOS上实现触控板与鼠标滚动方向独立控制的智能方案
  • QtScrcpy:跨设备协同革命,Android投屏的智能化效率新范式
  • 2026 企业项目管理工具选型:JIRA、飞书、JVS企业计划功能对比
  • 国内教学标本仪器公司排行:品类与服务实力对比 - 奔跑123
  • AI内容简报制作全攻略:4种方法加速WordPress博客创作与SEO排名提升
  • 3个核心方案:用DxWrapper解决Windows 10/11老游戏兼容性问题
  • 江西天一数控CNC加工机床进场实测与长期运维体验 - 资讯焦点
  • Redis--SDS字符串与集合的底层实现原理
  • Mobaxterm连接不上CentOS 7?先检查这3个服务(附Windows服务开启方法)
  • 企业云盘选型技术指南:2026年技术团队必须关注的7个核心指标
  • 2026年全国消防排烟风机源头厂家深度选购指南:深胜博实业与竞品横评 - 优质企业观察收录
  • 2026年全国消防排烟风机源头厂家对比:深胜博、德州欧卓、南方风机等实力品牌深度评测 - 优质企业观察收录
  • 公考机构性价比推荐上岸村与同类机构性价比对比 - 资讯焦点
  • 从‘街头算命’到‘AI命理师’:我是如何用ChatGPT和Kimi学习八字入门,并发现Prompt工程的关键
  • 2026年UPS电源/不间断电源/UPS电源租赁权威推荐榜单|TOP10选型报告 - 深度智识库
  • 终极指南:如何为iTerm2选择最适合你的终端配色方案