Linux 线程:从零构建多线程应用:系统化解析线程API与底层设计逻辑
线程
线程的概述
在之前,我们常把进程定义为 程序执行的实例,实际不然,进程实际上只是维护应用程序的各种资源,并不执行什么。真正执行具体任务的是线程。
那为什么之前直接执行a.out的时候,没有这种感受呢?
那是因为每一个进程中都会有一个主线程,我们默认执行的就是这个主线程。
线程创建比进程简单
进程通过返回值确定 是哪块进程的代码。
线程不需要,创建一个线程,比较简单,像回调函数一样,调用线程创建函数,在对应函数体中 操作这一线程即可。
从这往下的概述部分 重点(理解背诵)
进程是系统分配资源的基本单位,线程是CPU执行基本调度的基本单位
比如 如果线程是具体某个人,那么进程就是指部门
线程可以看作一个轻量级的进程(LWP:light weight process),在Linux环境下线程的本质仍是进程。
进程 必须至少包含一个线程

线程依赖于进程,线程共享进程的资源,线程的系统资源有(计数器,一组寄存器和栈)
进程结束 当前进程的所有线程 都将立即结束
Linux内核是不区分进程和线程的,只有在用户层面上进行区分。所以,进程所有操作函数pthread*是库函数,而并非系统调用
线程共享资源
- 文件描述符
- 每种信号的处理方式
- 当前工作目录
- 用户ID和组ID 内存地址空间
线程非共享资源
- 线程id
- 处理器现场和栈指针
- 独立的栈空间
- errno变量
- 信号屏蔽字
- 调度优先级
线程被CPU调度,因此线程中有调度优先级,且线程间不共享
查看指定进程的线程号的命令:ps -Lf pid(进程号)
线程的API
API介绍用的代码 较简短的代码我用图片展示。
只要看到了pthread.h 头文件,我们在编译的时候就需要加上 -lpthread
pthread_t 是无符号长整型
![]()
1、查看线程号
#include <pthread.h>
pthread_t pthread_self(void);
功能:
查看线程号
参数:
无参
返回值:
调用该函数的线程 的 线程ID
代码演示

代码运行结果

线程ID(通过pthread_self得到) 和 IPW(轻量级进程)的区别

大家看这张图,可以看到这两个值有明显的区别
在Linux中,线程就是LWP(轻量级进程),全局唯一,由操作系统内核分配,用于系统调度和资源管理。
而
线程ID呢仅在同一进程内有效,是抽象标识符。由 pthread 库在进程内维护。
2、创建线程
#include <pthread.h>
int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine) (void *),
void *arg);
功能:
创建一个线程
参数:
thread:线程标识符地址
attr:线程属性结构体地址,通常设置为NULL
属性这个参数,我们现在填写NULL,下面我会详细说一下这个参数。
start_routine:线程函数的入口地址
arg:传递给线程函数的参数
返回值:
成功:0
失败:非0
代码演示 案例1

注意这里主进程一定要阻塞,因为进程结束,线程也会关闭
代码运行结果

案例二 创建进程,每个线程有自己的线程函数

代码运行结果是一样的,大家只要知道能够这样用就可以了。
3、回收线程函数
函数介绍
功能:
等待线程结束(此函数会阻塞),并回收线程资源。如果线程已结束,那么该函数会立即返回。
参数:
thread:被等待的进程的进程ID
retval:用来存储线程退出状态的指针的地址
这里细说一下:retval的返回值类型我们可以看到是void **,这个变量需要用户创建,用来存储创建函数 线程执行函数的 返回值,返回值时void*类型。由于我们要得到它,就要提前创建一个void *的变量,再通过函数修改我们创建的变量为返回值的内容,由于是函数内部要函数外部的变量的值,因此需要传递所创建void *的变量的地址,因此时void **类型。
返回值:
成功:0
失败:非0
代码演示

代码运行结果

注意
由于带阻塞,因此有顺序,如下面这种情况

先等待tid1结束,回收tid1后,才会回收tid2
不管谁先结束,都是先1 后2
进程分离
创建好线程后,当多个任务同时进行,用上面的方法,会阻塞线程的释放,导致资源浪费(长时间不适用却霸占内存),因此这里 我们就将其分离出去,把释放工作交给系统,系统发现它结束,就会释放
由于它的归属权已经归于系统,此时我们就不可以再对它使用join
注意这里的分离,并不是该线程不依赖于进程,而是将 释放线程独立资源 的权限交给了系统,进程还是依赖与进程的,依旧共享进程的空间
函数介绍
#include <pthread.h>
int pthread_detach(pthread_t thread);
功能:
使调用线程的独立资源回收工作与当前进程分离
参数:
thread:线程ID
返回值:
成功:0
失败:非零
代码演示 主线程和子线程
本代码将实现 主线程和子线程 一起运行,并且利用主线程的正常工作,来验证pthread_detach的不阻塞的特性

代码运行结果

4、线程的取消和退出
注意要退出线程 一定不要调用exit或者_exit 这两个是退出进程的函数,如果调用这个在线程中知道你的进程是什么,它会将进程退出,进程退出会导致所有的线程退出,那么我们该怎么让单个线程退出呢?
1、线程的退出(自杀)
#include <pthread.h>
void pthread_exit(void *retval);
函数功能:
退出调用线程。一个进程中的多个线程是共享该进程的数据段的,因此通常线程退出后,所占用的资源并不会释放。
参数:
retval:存储线程退出状态的指针(return后的数据)
返回值:
无
2、线程的取消(他杀)
取消本线程,也可以取消当前进程的其他线程
#include <pthread.h>
int pthread_cancel(pthread_t thread);
功能:
退出调用线程。一个进程中的多个线程是共享该进程的数据段的,因此通常线程退出后,所占用的资源并不会释放。
参数:
thread:目标线程ID
返回值:
成功:0
失败:出错编号
注意
杀死线程也不是立刻就能完成,必须要到达取消点。
取消点:是线程检查是否被取消,并按请求进行动作的一个位置。通常是一些系统调用。
代码演示:
代码功能:子线程1实现5s后自杀,子线程2在7s时杀死子线程3,子线程2在10s时杀死自己。
这里我们会与遇到一个问题:当我们在线程2 中,我们首先需要传入本线程的名字(线程2),还需要传入子线程3的线程ID,我们该如何实现传两个参数呢?
答案在代码中,大家自己查看。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>//传递两个参数的办法就是借助结构体,将线程名和ID作为结构体成员后,将结构体传入 线程调用函数 中即可
//并且如果要实现tid3的修改同步到结构体内,需要传递tid3的指针类型
typedef struct dataDouble
{char name[32];pthread_t *id;
}DATA;//线程调用函数声明
void *my_fun1(void *arg);
void *my_fun2(void *arg);
void *my_fun3(void *arg);
int main(int argc, char const *argv[])
{//创建线程ID遍历(存放线程ID)pthread_t tid1,tid2,tid3;DATA *tid2_data = (DATA *)calloc(1,sizeof(DATA));tid2_data->id = &tid3;strcpy(tid2_data->name,"子进程2");//创建线程pthread_create(&tid1,NULL,my_fun1,(void *)"子线程1");pthread_create(&tid2,NULL,my_fun2,(void *)tid2_data);pthread_create(&tid3,NULL,my_fun3,(void *)"子线程3");//释放线程pthread_detach(tid1);pthread_detach(tid2);pthread_detach(tid3);//阻塞进程while(1);//释放结构体申请空间,一定要在全部线程结束之后free(tid2_data);return 0;
}
//线程调用函数体实现
void *my_fun1(void *arg)//线程1 在5s的时候自杀
{int i = 0;while(1){sleep(1);printf("----%s的运行时间为:%d\n",(char *)arg,++i);if(i == 5){pthread_exit(NULL);}}
}
void *my_fun2(void *arg)//线程2 在7s的时候杀死线程3,在10s的时候自杀(使用cancel)
{DATA data = *(DATA *)arg;int i = 0;while(1){sleep(1);printf("--------%s的运行时间为:%d\n",data.name,++i);if(i == 7){pthread_cancel(*data.id);}if(i == 10){pthread_cancel(pthread_self());}}
}
void *my_fun3(void *arg)
{int i = 0;while(1){sleep(1);printf("------------%s的运行时间为:%d\n",(char *)arg,++i);}
}
代码运行结果

结束
代码重在练习!
代码重在练习!
代码重在练习!
今天的分享就到此结束了,希望对你有所帮助,如果你喜欢我的分享,请点赞收藏夹关注,谢谢大家!!!
下篇介绍:线程的属性介绍,线程池的简述,多线程的建立
相关文章:
Linux 线程:从零构建多线程应用:系统化解析线程API与底层设计逻辑
线程 线程的概述 在之前,我们常把进程定义为 程序执行的实例,实际不然,进程实际上只是维护应用程序的各种资源,并不执行什么。真正执行具体任务的是线程。 那为什么之前直接执行a.out的时候,没有这种感受呢…...
VMware虚拟机Ubuntu磁盘扩容
VMware中操作: 选择要扩容的虚拟机,点击编辑虚拟机设置 打开后点击磁盘——>点击扩展(注意:如果想要扩容的话需要删除快照) 调整到你想要的容量 点击上图的扩展——>确定 然后我们进到虚拟机里面 首先&#…...
游戏引擎学习第217天
运行游戏并在 FreeVariableGroup 中遇到我们的断言 其实在美国,某些特定的小糖果(例如小糖蛋)只在圣诞节和复活节期间出售,导致有些人像我一样在这段时间吃得过多,进而增加体重。虽然这种情况每年都会发生,…...
Day 8 上篇:深入理解 Linux 驱动模型中的平台驱动与总线驱动
B站相应的视屏教程: 📌 内核:博文视频 - 总线驱动模型实战全解析 —— 以 PCA9450 PMIC 为例 敬请关注,记得标为原始粉丝。 在 Linux 内核驱动模型中,设备与驱动的组织方式不是随意堆砌,而是基于清晰的分类…...
freertos内存管理简要概述
概述 内存管理的重要性 在嵌入式系统中,内存资源通常是有限的。合理的内存管理可以确保系统高效、稳定地运行,避免因内存泄漏、碎片化等问题导致系统崩溃或性能下降。FreeRTOS 的内存管理机制有助于开发者灵活地分配和释放内存,提高内存利用…...
Dify问题记录 (一)
问题背景 Dify智能体将含有中文的JSON参数传递到Java后端时出现乱码。 解决办法 在HTTP节点前添加代码执行节点,将参数强制编码为UTF-8格式。在Java后端代码中进行解码操作,以确保参数的正确性。 代码如下: 代码执行节点中代码 function main({arg…...
全新突破 | 更全面 · 更安全 · 更灵活
xFile 高可用存储网关 2.0 重磅推出,新增多空间隔离功能从根源上防止数据冲突,保障各业务数据的安全性与独立性。同时支持 NFS、CIFS、FTP 等多种主流文件协议,无需繁琐的数据拷贝转换,即可与现有系统无缝对接,降低集成…...
使用Python建立双缝干涉模型
引言 双缝干涉实验是物理学中经典的实验之一,它展示了光的波动性以及量子力学的奇异性。实验结果表明,当光或粒子通过两条狭缝时,它们会产生干涉现象,形成明暗相间的条纹图案。这种现象不仅说明了光的波动性,还揭示了量子力学的核心思想——粒子具有波动性。今天,我们将…...
T-Box车载系统介绍及其应用
定义 T-Box汽车系统,全称为Telematics - BOX,也常简称为车载T - BOX,是汽车智能系统及车联网系统中的核心组成部分,是安装在车辆上的一种高科技远程信息处理器。 工作原理 T-Box的核心功能主要通过MPU和MCU实现。MPU负责应用程序功…...
SQLyog使用教程
准备工作 链接本地数据库 准备 1:安装mySQL数据库 2:安装SQLyong 连接本地数据库 打开SQLyong应用,将会出现下面的页面 点击新建,输入链接名 输入密码,点击 连接 按钮 如果出现连接错误,且错误号为2058…...
for循环的优化方式、循环的种类、使用及平替方案。
本篇文章主要围绕for循环,来讲解循环处理数据中常见的六种方式及其特点,性能。通过本篇文章你可以快速了解循环的概念,以及循环在实际使用过程中的调优方案。 作者:任聪聪 日期:2025年4月11日 一、循环的种类 1.1 默认有以下类型 原始 for 循环 for(i = 0;i<10;i++){…...
使用 Python 扫描 Windows 下的 Wi-Fi 网络实例演示
使用 Python 扫描 Windows 下的 Wi-Fi 网络 代码实现代码解析 1. 导入库2. 解码混合编码3. 扫描 Wi-Fi 网络4. 运行函数 这是我当前电脑的 wifi 连接界面。 这个是运行的效果图: 代码实现 我们使用了 Python 的 subprocess 模块来调用 Windows 的内置命令 netsh…...
python manimgl数学动画演示_微积分_线性代数原理_ubuntu安装问题[已解决]
1.背景 最近调研python opencv, cuda加速矩阵/向量运算, 对于矩阵的线性变换, 秩, 转秩, 行列式变化等概概念模糊不清. 大概课本依旧是天书, 于是上B站搜索线性代数, 看到 3Blue1Brown 线性变换本质 视频, 点击观看. 惊为天人 --> 豁然开朗 --> 突然顿悟 --> 开心不已…...
【vue3】@click函数传动态变量参数
根据java的学习,摸索了一下vue3 函数传参的方式。以此作为记录。有更好的其它方式,可以评论区补充。 <script> const tmpref(); </script><button click"tmpFunction(传递参数:tmp)">按钮</button> // 直接【字符串…...
用matplotlib生成一个炫酷的爱心
下面是结合数学方程和可视化技巧,生成一个炫酷的爱心效果: import numpy as np import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation # 创建画布 fig plt.figure(figsize(8, 8)) ax plt.axes(xlim(-2.5, 2.5), ylim(-3,…...
【leetcode hot 100 300】最长递增子序列
错误解法:在每次更新db[i]时,如果当前nums[i]>nums[i-1]就db[i-1]1,否则db[i-1] class Solution {public int lengthOfLIS(int[] nums) {int n nums.length;int[] db new int[n]; // db[i]表示到i的最长严格递增子序列的长度db[0] 1;f…...
oracle 12c密码长度,复杂度查看与设置
一 密码长度和复杂度 Oracle 数据库通过 PASSWORD_VERIFY_FUNCTION 来控制密码复杂度。 1.1 查看当前的密码复杂度设置 SELECT * FROM dba_profiles WHERE resource_name PASSWORD_VERIFY_FUNCTION; LIMIT表示分配给该 PROFILE 的密码验证函数名称。如果为 NULL,…...
数据结构——哈希技术及链地址法
目录 一、哈希的定义 二、哈希冲突定义 三、构造哈希函数的方法 四、四种解决哈希冲突的方法 4.1 开放地址法 4.2 链地址法 4.3 再散列函数法 4.4 公共区溢出法 五、链地址法结构体设计 六、基本操作的实现 6.1 哈希函数 6.2 初始化 6.3 插入值 6.4 删除值 6.5 查…...
开源CMS的模块化设计和API接口如何具体影响其扩展性?
优秀的CMS系统都有自己主打的特点,开源CMS凭借其灵活性和低成本优势占据了市场主流地位,而模块化设计与API接口正是其扩展性的两大基石。本文将深入探讨这两大技术特性是如何影响cms的扩展性的。 一、模块化设计:功能解耦与生态繁荣的引擎 …...
【Docker】快速部署 Certbot 并为 Nginx 服务器配置 SSL/TLS 证书
【Docker】快速部署 Certbot 并为 Nginx 服务器配置 SSL/TLS 证书 引言 Certbot 是一个免费的开源工具,用于自动化管理和获取 SSL/TLS 证书,主要用于与 Let’s Encrypt 证书颁发机构交互。 步骤 Nginx 挂载 certbot 文件夹。 docker run -d \--name…...
Redis下载稳定版本5.0.4
https://www.redis.net.cn/download/ Redis下载 Redis 版本号采用标准惯例:主版本号.副版本号.补丁级别,一个副版本号就标记为一个标准发行版本,例如 1.2,2.0,2.2,2.4,2.6,2.8,奇数的副版本号用来表示非标准版本,例如2.9.x发行版本是Redis 3.0标准版本的非标准发行版本…...
Google Chrome下载受限制的解决方案【方法指南】
在国内使用网络时,部分用户在尝试访问Google Chrome官网下载谷歌浏览器时,常常遇到网页无法打开或文件下载失败的情况。这种下载受限制的问题多由网络访问政策或DNS解析异常导致。为了正常获取Google Chrome的最新版安装程序,用户需要通过一些…...
Linux关于git上传大文件的解决方案:使用Git LFS
最近想要上传sdk到gitlab仓库上,但是使用git push的时候发现限制文件上传的大小限制到了100MB。 保持当前仓库的干净,要么重新拉取,要么git reset HEAD^ --hard这个命令来重新进行commit,直到撤回到代码上显示没有commit的地方为止…...
JAVA后端八股面试经验总结-前言篇
1️⃣个人暑期实习面试情况 暑期实习面试告一段段落了 陆陆续续大小厂有20+Java后端开发的面经 2️⃣为什么要写这个呢? ①首先,了解最重点的面试最爱问的题型有哪些? 我会整理出我面到过至少2次的题目和回答方式࿰…...
《计算机名人堂》专栏介绍:先驱之路
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 目录 🌟引言:先驱之路一、出发点:讲述数字世界的缔…...
参照Spring Boot后端框架实现序列化工具类
本文参照Jackson实现序列化工具类,旨在于简化开发 JacksonUtil.class public class JacksonUtil {private JacksonUtil() {}/*** 单例*/private final static ObjectMapper OBJECT_MAPPER;static {OBJECT_MAPPER new ObjectMapper();}private static ObjectMappe…...
mysql 删除表等待
今天有个表加字段,语句是先删除,后重新建,表没有数据 ,但是删除一个表的时候,语句drop table 提示超时 show processlist 后,等待类型是 Waiting for table metadata lock 取消重试几次后仍然是如此…...
MCP工具的配置文件格式是怎么样的?MCP教程平台推荐
MCP(Model Context Protocol)配置文件是AI开发中连接MCP服务器的核心文件,采用JSON格式定义服务参数。它广泛应用于Cursor、ChatWise等AI开发工具,帮助开发者快速配置本地或远程MCP服务。本文将深入解析MCP配置文件的结构、获取方…...
网络安全法规与入门指南
在当今数字化时代,网络安全已成为保障个人隐私、企业利益和国家安全的关键领域。随着网络攻击的日益复杂和频繁,了解和遵守网络安全法规变得尤为重要。本文将深入探讨网络安全相关法规,并为想要进入这一领域的读者提供实用的入门指南。 一、…...
医院访客登记如何做才能更高效?
在医院工作过的朋友,大概都有过这样的体验:一到探视时间,门诊大厅、病房入口就开始拥堵,尤其是一些管控较严的科室,如ICU、手术区、儿科病房,来访人员必须逐一登记信息。人一多,就容易出错、漏登…...
