Linux 线程:从零构建多线程应用:系统化解析线程API与底层设计逻辑

news2025/4/12 22:33:51

线程

线程的概述

在之前,我们常把进程定义为 程序执行的实例,实际不然,进程实际上只是维护应用程序的各种资源,并不执行什么。真正执行具体任务的是线程。

那为什么之前直接执行a.out的时候,没有这种感受呢?

那是因为每一个进程中都会有一个主线程,我们默认执行的就是这个主线程。

线程创建比进程简单

进程通过返回值确定 是哪块进程的代码。

线程不需要,创建一个线程,比较简单,像回调函数一样,调用线程创建函数,在对应函数体中 操作这一线程即可。

从这往下的概述部分 重点(理解背诵)

进程是系统分配资源的基本单位,线程是CPU执行基本调度的基本单位

比如 如果线程是具体某个人,那么进程就是指部门

线程可以看作一个轻量级的进程(LWP:light weight process),在Linux环境下线程的本质仍是进程

进程 必须至少包含一个线程

线程依赖于进程,线程共享进程的资源,线程的系统资源有(计数器,一组寄存器和栈)

进程结束 当前进程的所有线程 都将立即结束

Linux内核是不区分进程和线程的,只有在用户层面上进行区分。所以,进程所有操作函数pthread*是库函数,而并非系统调用

线程共享资源

  1. 文件描述符
  2. 每种信号的处理方式
  3. 当前工作目录
  4. 用户ID和组ID 内存地址空间

线程非共享资源

  1. 线程id
  2. 处理器现场和栈指针
  3. 独立的栈空间
  4. errno变量
  5. 信号屏蔽字
  6. 调度优先级

线程被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);
    }
}

代码运行结果

结束

代码重在练习!

代码重在练习!

代码重在练习!

今天的分享就到此结束了,希望对你有所帮助,如果你喜欢我的分享,请点赞收藏夹关注,谢谢大家!!!

下篇介绍:线程的属性介绍,线程池的简述,多线程的建立

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2333411.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VMware虚拟机Ubuntu磁盘扩容

VMware中操作&#xff1a; 选择要扩容的虚拟机&#xff0c;点击编辑虚拟机设置 打开后点击磁盘——>点击扩展&#xff08;注意&#xff1a;如果想要扩容的话需要删除快照&#xff09; 调整到你想要的容量 点击上图的扩展——>确定 然后我们进到虚拟机里面 首先&#…

游戏引擎学习第217天

运行游戏并在 FreeVariableGroup 中遇到我们的断言 其实在美国&#xff0c;某些特定的小糖果&#xff08;例如小糖蛋&#xff09;只在圣诞节和复活节期间出售&#xff0c;导致有些人像我一样在这段时间吃得过多&#xff0c;进而增加体重。虽然这种情况每年都会发生&#xff0c…

Day 8 上篇:深入理解 Linux 驱动模型中的平台驱动与总线驱动

B站相应的视屏教程&#xff1a; &#x1f4cc; 内核&#xff1a;博文视频 - 总线驱动模型实战全解析 —— 以 PCA9450 PMIC 为例 敬请关注&#xff0c;记得标为原始粉丝。 在 Linux 内核驱动模型中&#xff0c;设备与驱动的组织方式不是随意堆砌&#xff0c;而是基于清晰的分类…

全新突破 | 更全面 · 更安全 · 更灵活

xFile 高可用存储网关 2.0 重磅推出&#xff0c;新增多空间隔离功能从根源上防止数据冲突&#xff0c;保障各业务数据的安全性与独立性。同时支持 NFS、CIFS、FTP 等多种主流文件协议&#xff0c;无需繁琐的数据拷贝转换&#xff0c;即可与现有系统无缝对接&#xff0c;降低集成…

T-Box车载系统介绍及其应用

定义 T-Box汽车系统&#xff0c;全称为Telematics - BOX&#xff0c;也常简称为车载T - BOX&#xff0c;是汽车智能系统及车联网系统中的核心组成部分&#xff0c;是安装在车辆上的一种高科技远程信息处理器。 工作原理 T-Box的核心功能主要通过MPU和MCU实现。MPU负责应用程序功…

SQLyog使用教程

准备工作 链接本地数据库 准备 1&#xff1a;安装mySQL数据库 2&#xff1a;安装SQLyong 连接本地数据库 打开SQLyong应用&#xff0c;将会出现下面的页面 点击新建&#xff0c;输入链接名 输入密码&#xff0c;点击 连接 按钮 如果出现连接错误&#xff0c;且错误号为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 连接界面。 这个是运行的效果图&#xff1a; 代码实现 我们使用了 Python 的 subprocess 模块来调用 Windows 的内置命令 netsh…

python manimgl数学动画演示_微积分_线性代数原理_ubuntu安装问题[已解决]

1.背景 最近调研python opencv, cuda加速矩阵/向量运算, 对于矩阵的线性变换, 秩, 转秩, 行列式变化等概概念模糊不清. 大概课本依旧是天书, 于是上B站搜索线性代数, 看到 3Blue1Brown 线性变换本质 视频, 点击观看. 惊为天人 --> 豁然开朗 --> 突然顿悟 --> 开心不已…

用matplotlib生成一个炫酷的爱心

下面是结合数学方程和可视化技巧&#xff0c;生成一个炫酷的爱心效果&#xff1a; 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】最长递增子序列

错误解法&#xff1a;在每次更新db[i]时&#xff0c;如果当前nums[i]>nums[i-1]就db[i-1]1&#xff0c;否则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&#xff0c;…

数据结构——哈希技术及链地址法

目录 一、哈希的定义 二、哈希冲突定义 三、构造哈希函数的方法 四、四种解决哈希冲突的方法 4.1 开放地址法 4.2 链地址法 4.3 再散列函数法 4.4 公共区溢出法 五、链地址法结构体设计 六、基本操作的实现 6.1 哈希函数 6.2 初始化 6.3 插入值 6.4 删除值 6.5 查…

【Docker】快速部署 Certbot 并为 Nginx 服务器配置 SSL/TLS 证书

【Docker】快速部署 Certbot 并为 Nginx 服务器配置 SSL/TLS 证书 引言 Certbot 是一个免费的开源工具&#xff0c;用于自动化管理和获取 SSL/TLS 证书&#xff0c;主要用于与 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下载受限制的解决方案【方法指南】

在国内使用网络时&#xff0c;部分用户在尝试访问Google Chrome官网下载谷歌浏览器时&#xff0c;常常遇到网页无法打开或文件下载失败的情况。这种下载受限制的问题多由网络访问政策或DNS解析异常导致。为了正常获取Google Chrome的最新版安装程序&#xff0c;用户需要通过一些…

《计算机名人堂》专栏介绍:先驱之路

名人说&#xff1a;路漫漫其修远兮&#xff0c;吾将上下而求索。—— 屈原《离骚》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 &#x1f31f;引言&#xff1a;先驱之路一、出发点&#xff1a;讲述数字世界的缔…

MCP工具的配置文件格式是怎么样的?MCP教程平台推荐

MCP&#xff08;Model Context Protocol&#xff09;配置文件是AI开发中连接MCP服务器的核心文件&#xff0c;采用JSON格式定义服务参数。它广泛应用于Cursor、ChatWise等AI开发工具&#xff0c;帮助开发者快速配置本地或远程MCP服务。本文将深入解析MCP配置文件的结构、获取方…

网络安全法规与入门指南

在当今数字化时代&#xff0c;网络安全已成为保障个人隐私、企业利益和国家安全的关键领域。随着网络攻击的日益复杂和频繁&#xff0c;了解和遵守网络安全法规变得尤为重要。本文将深入探讨网络安全相关法规&#xff0c;并为想要进入这一领域的读者提供实用的入门指南。 一、…

医院访客登记如何做才能更高效?

在医院工作过的朋友&#xff0c;大概都有过这样的体验&#xff1a;一到探视时间&#xff0c;门诊大厅、病房入口就开始拥堵&#xff0c;尤其是一些管控较严的科室&#xff0c;如ICU、手术区、儿科病房&#xff0c;来访人员必须逐一登记信息。人一多&#xff0c;就容易出错、漏登…