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

Linux操作系统学习(线程基础)

文章目录

  • 线程的基础概念
    • 线程控制
    • 内核LWP和线程ID的关系

线程的基础概念

​ 一般教材对线程描述是:是在进程内部运行的一个分支(执行流),属于进程的一部分,粒度要比进程更加细和轻量化

​ 一个进程中是可能存在多个线程的,可能是1:1也可能是1:n,所以线程也应由OS管理,管理就离不开“先描述,再组织”,所以线程也应该类似PCB一样有线程控制块TCB,但Linux中没有专门为线程设计的TCB,而是用进程PCB来模拟线程

​ 通过某些函数方法,可以在同一个进程中只创建task_struct,让这些PCB共享同一个地址空间,把当前进程的资源(代码+数据)划分成若干份,让每个PCB使用执行一部分。

​ 站在CPU的角度,CPU只看PCB,不关心是否共享一份地址空间,此时CPU看到的PCB就是一个需要被调度的执行流

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ovbkmZxa-1677869543495)(G:\Typora\图片保存\image-20221222011520050.png)]

​ Linux中是用进程来模拟线程的,所以也叫轻量级进程(站在用户角度是线程),不用单独为线程设计算法,可直接用进程的一套相关方法,所以不用维护复杂的进程和线程的关系。OS只需要聚焦在线程间的资源分配上就可以了,所以CPU是只看PCB,不区分线程和进程。

进程与线程的区别:

  • 进程具有独立性,可以有部分资源共享(基于管道、ipc资源下)

  • 线程共享进程的数据,但是也有属于自己的数据

  • 进程是资源分配的基本单位,承担系统资源分配的基本实体

  • 线程是CPU调度的基本单位,承担进程资源一部分的基本实体

线程的优点:

  • 创建一个新的线程的代价比创建一个进程小得多。创建一个线程虽然也需要创建数据结构,但是并不需要重新开辟资源,只需要将进程的部分资源分配给线程。创建一个进程不仅需要创建大量数据结构,还需要重新创建资源。

  • 与进程之间的切换相比,线程之间的切换需要操作系统做的工作少。线程只是进程的部分资源,切换的资源少。

  • 线程占用的资源比进程少

  • 能充分利用多处理器的可并行数量

  • 在等待慢速的I/O任务结束的同时,程序可以执行其它的计算任务

  • 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现。

  • I/O密集型应用,为了提高性能,将I/O操作重叠,线程可以同时等待不同的I/O操作。I/O操作是与外设交互数据,会很慢。

线程的缺点:

  • 性能缺失
    一个处理器只能处理一个线程,如果线程数比可用处理器数多,会有较大的性能损失,会增加额外的同步和CPU调度的开销,而资源却是不变的

  • 健壮性降低
    编写多线程时, 可能因为共享的变量, 导致一个线程修改此变量, 影响另外一个线程。(线程不是对立的,所以线程之间缺乏保护)

    ​ (多线程之间的变量是同一个变量;多进程之间变量不是同一个变量,一开始是共享父进程的,但在改变时发生写时拷贝)

  • 缺乏访问的控制
    进程时访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响

  • 编程难度相对高

线程异常:

  • 单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃 ;线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,该进程内的所有线程也就随即退出

线程用途:

  • 合理的使用多线程,能提高CPU密集型程序的执行效率
  • 合理的使用多线程,能提高IO密集型程序的用户体验(如生活中我们一边写代码一边下载开发工具,就是 多线程运行的一种表现)

线程与进程的关系

在这里插入图片描述


线程控制

​ 因为是用进程模拟的线程,所以Linux下不会给我们提供直接操作线程的接口,而给线程提供的是在同一个地址空间创建PCB的方法、分配资源给指定的PCB的接口等等,需要用户自己创建一些函数方法如创建线程、释放线程、等待线程等,对用户不太友好。

​ 所以一般我们在使用的线程相关函数方法,都是由系统级别的工程师在用户层对Linux轻量级进程提供的方法进行封装打包成的库。

下面主要介绍pthread库

  1. 创建线程函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wsdkuadx-1677869543496)(G:\Typora\图片保存\image-20221223164100513.png)]

  • threaad:输出型参数,会获取刚创建的线程ID
  • attr:传入的是线程的属性(优先级、运行栈等等)一般传入NULL,交给OS默认处理。
  • start_routine:创建线程后所执行的方法
  • arg:一般会传给第三个参数的函数方法
#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>void* ThreadRun(void* args)
{std::string name = (char*)args;while(true){std::cout << "当前处于新线程,新线程ID是:" << pthread_self() << ", args: " << name << std::endl;sleep(1);}
}int main()
{pthread_t tid[5];for(int i = 0;i < 5;i++)pthread_create(&tid[i],nullptr,ThreadRun,(void*)"new thread");while(true){std::cout << "当前处于主线程,主线程ID是:" << pthread_self() << std::endl;std::cout << "###################################################################" << std::endl;for(size_t i = 0;i < 5;i++)std::cout << "创建的新线程[" << i << "]ID是:" << tid << std::endl;std::cout << "###################################################################" << std::endl;sleep(3);}return  0;
}

犹豫是多线程打印,会有乱码现象。

代码中用到的pthread_self(),作用是在谁的线程下用,就获取谁的线程 ID

在这里插入图片描述

​ 可以看到的是,他们的PID都是一样的,所以线程是对进程的资源分配 ,LWP是内核提供的,和打印出来的id是不一样的,这里用的是原生线程库,打出来的id是其实是进程地址号

  1. 线程等待

进程退出有三种方式

  • 代码正常运行,结果正确
  • 代码正常运行,结果不对
  • 程序异常,直接退出

​ 前两点退出时不会产生信号,但会产生退出码,而程序异常会产生终止信号;线程分配的资源是进程的一部分,所以只要有一个线程执行的代码部分导致程序崩溃,整个进程会直接退出并产生终止信号,整个进程崩溃,所有线程也就崩溃了。

​ 一般而言,线程也是需要等待的,否则也会出现类似僵尸进程那样的问题,已经退出的线程,其空间没有被释放,仍然在进程的地址空间内,占用资源还得维护数据

​ 线程等待只能是在代码正常运行的前提下等待,而程序异常,进程就会直接退出了,OS会向进程发送退出信号,此时与线程无关,下图所示线程等待函数:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g8lH0kwN-1677869543496)(G:\Typora\图片保存\image-20221223182409990.png)]

  • thread:需要等待的线程的ID

  • retval:输出型参数,获取创建线程时指定的函数方法的返回值,(因为指定的函数方法返回值是指针返回,所以这里是二级指针)

    pthread_join函数在等待时是阻塞式等待

#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>void* ThreadRun(void* args)
{sleep(3);return (void*)"finish";
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,ThreadRun,(void*)"new thread");void* status = NULL;pthread_join(tid,&status);std::cout << (char*)status << std::endl;return  0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jVsAbkzU-1677869543496)(G:\Typora\图片保存\image-20221223182123417.png)]

  1. 线程退出

    线程退出的方法有三种

  • 函数中return

    • main函数return代表主线程和进程退出

    • 其他线程函数中return,代表当前线程退出

  • 通过pthread_exit函数终止线程

!](https://img-blog.csdnimg.cn/24870b7ee8624fc297d3f4b109cc4ce6.png)

参数是要传入的退出状态

void* ThreadRun(void* args)
{std::string name = (char*)args;std::cout << name << " running. . ." << std::endl;sleep(3);pthread_exit((void*)123);std::cout << "exit fail" << std::endl;return (void*)"finish";
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,ThreadRun,(void*)"new thread");void* status = NULL;pthread_join(tid,&status);printf("%d\n",(int*)status);return  0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wFkoQuxU-1677869543497)(G:\Typora\图片保存\image-20221223194429944.png)]

  • 通过pthread_cancel函数取消线程

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FmTkx3LF-1677869543497)(G:\Typora\图片保存\image-20221223195240763.png)]

    只需要传入要取消的线程的id即可

    成功返回0,失败返回-1

    void* ThreadRun(void* args)
    {std::string name = (char*)args;std::cout << name << " running. . ." << std::endl;sleep(10);return (void*)"finish";
    }int main()
    {pthread_t tid;pthread_create(&tid,nullptr,ThreadRun,(void*)"new thread");sleep(1);std::cout << "cancel sub thread. . ." << std::endl;sleep(5);pthread_cancel(tid);void* status = NULL;pthread_join(tid,&status);printf("%d\n",(int*)status);return  0;
    }
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LhJGzBKU-1677869543497)(G:\Typora\图片保存\image-20221223195949508.png)]

    取消后的线程退出码是 -1 代表是PTHREAD_ CANCELED的意思

注意:不建议在子线程中取消主线程的做法,这样会导致进入僵尸进程的状态。因为主线程会退出而没有资源可以等待子线程退出,子线程就造成资源浪费了

void* ThreadRun(void* args)
{sleep(5);pthread_cancel((pthread_t)args);while(1){std::cout << " running. . ." << std::endl;sleep(1);}return (void*)"finish";
}int main()
{pthread_t tid;pthread_t g_tid = pthread_self();pthread_create(&tid,nullptr,ThreadRun,(void*)g_tid);void* status = NULL;pthread_join(tid,&status);printf("%d\n",(int*)status);return  0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6sFL15Cs-1677869543498)(G:\Typora\图片保存\image-20221223203927069.png)]

如图所示:进入了僵尸进程,以及类似僵尸进程的线程状态(主线程 defunct)

  1. 线程分离

若不想阻塞式的等待线程退出,可以使用线程分离函数;分离之后的线程不需要被join,运行完毕后会自动释放资源

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WDoqmLHI-1677869543498)(G:\Typora\图片保存\image-20221223222753176.png)]

传入要分离的线程id即可

void* ThreadRun(void* args)
{pthread_detach(pthread_self());std::cout << " running. . ." << std::endl;sleep(1);return (void*)"finish";
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,ThreadRun,(void*)"hello");sleep(3);void* status = NULL;int ret = pthread_join(tid,&status);printf("ret:%d,status:%s\n",ret,(char*)status);return  0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X1F2IqD1-1677869543498)(G:\Typora\图片保存\image-20221223222813924.png)]

只要线程分离了,就不可在使用join去等待了,会等待失败

内核LWP和线程ID的关系

​ 我们使用的pthread原生线程库是存在磁盘当中的,在使用时会被加载到内存当中,通过页表映射到进程地址空间的共享区(堆区和栈区之间的区域)所有的进程都共用同一份库(只是进程空间中进程自己认为独占这个库)

​ 而每个线程共享的是同一份进程地址空间,但是每个线程也会有自己的私有数据部分如线程栈(主线程栈是在栈区的)、上下文数据、属性等等。这些线程私有的部分组成线程的数据块(可以想象成类似于task_struct的线程struct),数据块其实是存储在线程库中的,通过页表映射到进程地址的共享区。

(创建线程的时候,用mmap系统调用从heap分配出来的)

​ 在使用pthread_create创建线程时,线程库会生成一个ID号来表示新创建的线程,这个ID由函数的第一个参数可以获取到;而线程ID就是这些数据块在共享区映射的地址,每个线程id就是每个线程的数据块在共享区的地址,通过id号便能找到线程的所有数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z1FzwW8l-1677869543498)(G:\Typora\图片保存\image-20221224021321882.png)]

线程id就是:线程存储在线程库中的数据,通过内存映射到进程地址空的共享区的地址标号,有了这个标号就能找到线程的所有的私有数据

LWP和线程id的关系是什么?

LWP是在内核层面的,线程id是在用户层面的。

​ 前面说过在Linux中没有提供专门的线程方法,使用进程模拟的线程,所以Liunx中的线程其实是轻量级进程,依旧用的task_struct,所以CPU调度知认PCB;那进程由PID,线程也该有个数字来标识这些task_struct,那这个标识就是LWP,所以LWP实际上是供CPU调度使用的(类似文件描述符一样)

​ 线程id是用户层的,LWP是内核层的,那id怎么知道他是对应哪个task_struct的呢?所以线程的数据块一定也包含一个LWP,形成1:1的关系(也有1:n,即一个线程id包含多个LWP,在用户层面看来是一个线程)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lfYM5XQC-1677869543499)(G:\Typora\图片保存\image-20221224023454692.png)]

相关文章:

Linux操作系统学习(线程基础)

文章目录线程的基础概念线程控制内核LWP和线程ID的关系线程的基础概念 ​ 一般教材对线程描述是&#xff1a;是在进程内部运行的一个分支&#xff08;执行流&#xff09;&#xff0c;属于进程的一部分&#xff0c;粒度要比进程更加细和轻量化 ​ 一个进程中是可能存在多个线程…...

YOLOv5源码逐行超详细注释与解读(1)——项目目录结构解析

前言 前面简单介绍了YOLOv5的网络结构和创新点&#xff08;直通车&#xff1a;【YOLO系列】YOLOv5超详细解读&#xff08;网络详解&#xff09;&#xff09; 在接下来我们会进入到YOLOv5更深一步的学习&#xff0c;首先从源码解读开始。 因为我是纯小白&#xff0c;刚开始下…...

前端开发总结的一些技巧和实用方法(2)

本文主要介绍一些JS中用到的小技巧和实用方法&#xff0c;可以在日常Coding中提升幸福度&#xff0c;也可以通过一些小细节来增加代码可读性&#xff0c;让代码看起来更加优雅&#xff0c;后续将不断更新1.数组 map 的方法 (不使用Array.Map) Array.from 还可以接受第二个参数…...

Docker搭建jenkins(Vue自动化部署)

前言 需要提前准备的条件 Docker环境 一、jenkins镜像 # 查询镜像 docker search jenkins# 下载镜像 # lts稳定版 docker pull jenkins/jenkins:lts#查看镜像 docker images二、启动Jenkins容器 创建挂载文件夹&#xff0c;并且进行文件授予权限 #创建文件夹 mkdir -p /home/j…...

ADCS攻击之CVE-2022–26923

CSDN自动博客文章迁移漏洞简介该漏洞允许低权限用户在安装了 Active Directory 证书服务 (AD CS) 服务器角色的默认 Active Directory 环境中将权限提升到域管理员。在默认安装的ADCS里就启用了Machine模板。漏洞利用添加机器账户&#xff0c;并将该机器账户dnsHostName指向DC[…...

AO3401-ASEMI低压P沟道MOS管AO3401

编辑&#xff1a;ll AO3401-ASEMI低压P沟道MOS管AO3401 型号&#xff1a;AO3401 品牌&#xff1a;ASEMI 封装&#xff1a;SOT-23 最大漏源电流&#xff1a;-4.2A 漏源击穿电压&#xff1a;-30V RDS&#xff08;ON&#xff09;Max&#xff1a;0.05Ω 引脚数量&#xff1…...

【STM32MP157应用编程】3.控制PWM

目录 PWM文件 指令操作PWM 程序操作PWM 程序说明 程序代码 3_PWM_1.c 启动交叉编译工具 编译 拷贝到开发板 测试 PWM文件 在/sys/class/pwm目录下&#xff0c;存放了PWM的文件。 pwmchip0和pwmchip4目录对应了MP157 SoC的2个PWM控制器&#xff0c;pwmchip0对应的是M…...

基于Python的selenium

一、安装 1.1安装Python&#xff0c;安装Python时需要勾选增加环境变量 如果之前已经安装过Python&#xff0c;需要将Python相关文件以及环境变量删除 1.2安装成功&#xff1a;在命令行界面下输入Python&#xff0c;最终展示>>>即可成功 2.1安装pycharm,直接自定义安装…...

Go底层原理:一起来唠唠GMP调度(一)

目录前言一、进程、线程、Goroutine1、进程与线程2、Goroutine二、Go调度器设计思想1、线程模型1.1 内核级线程模型1.2 用户级线程模型1.3 混合型线程模型2、 被废弃的 G-M 调度器2.1 了解 G-M 调度如何工作3、如今高效的 GMP 模型3.1 GMP模型调度流程3.2 GMP调度设计策略3.3 G…...

前端——1.相关概念

这篇文章主要介绍前端入门的相关概念 1.网页 1.1什么是网页&#xff1f; 网站&#xff1a;是指在因特网上根据一定的规则&#xff0c;使用HTML等制作的用于展示特定内容相关的网页集合 网页&#xff1a;是网站中的一“页”&#xff0c;通常是HTML格式的文件&#xff0c;它要…...

java四种线程池(基本使用)

标题java四种线程池及使用示例 1、线程工厂 1、我们先来写ThreadFactory&#xff0c;在创建线程池时候可以传入自定义的线程工厂&#xff0c;线程工厂说白了就是用来定制线程的一些属性&#xff1a;名字、优先级、是否为守护线程。直接看代码即可。 当然创建线程池的时候可以…...

float的表示范围为什么比long大

●很多人会有一个疑问, 一个用来表示小数的 float 为什么表示的范围会比 long 还要大呢 ? ●这次, 咱们就来详细说一说这个事情 从长计议 ●聊到这个话题, 我们就要从计算机存储数字这个位置说起了 ●计算机存储数字的方式其实就是 : 二进制 二进制是计算机中最基本的数字存储…...

Flutter Android 打包保姆式全流程 2023 版

大家好&#xff0c;我是 17。 为什么要写这篇文章呢&#xff1f;对于一没有 android 开发经验&#xff0c;从未有过打包经历的新人来说&#xff0c;要想成功打包&#xff0c;是很困难的。因为受到的阻碍太多&#xff0c;是完全陌生的领域&#xff0c;几乎是寸步难行。如果有老…...

C++笔记之lambda表达式

引言 Lambda表达式是从C 11版本引入的特性&#xff0c;利用它可以很方便的定义匿名函数对象&#xff0c;通常作为回调函数来使用。大家会经常拿它和函数指针&#xff0c;函数符放在一起比较&#xff0c;很多场合下&#xff0c;它们三者都可以替换着用。 语法 [ captures ] (…...

flink大数据处理流式计算详解

flink大数据处理 文章目录flink大数据处理二、WebUI可视化界面&#xff08;测试用&#xff09;三、Flink部署3.1 JobManager3.2 TaskManager3.3 并行度的调整配置3.4 区分 TaskSolt和parallelism并行度配置四、Source Operator(资源算子)五、Sink Operator(输出算子)六、Flink滑…...

Java面试题(二十三)DCL单例

懒汉式单例 private static SingletonInstance INSTANCE;private SingletonInstance(){}public static SingletonInstance getInstance() {if (INSTANCE null) {INSTANCE new SingletonInstance();}return INSTANCE;}构造方法私有化&#xff0c;然后判断是否为空&#xff0c;…...

UML-类图

一、类 一个类由三个格子组成&#xff0c;从上至下分别表示&#xff1a; 第一格&#xff1a;类名称&#xff08;接口和抽象类&#xff0c;使用斜体&#xff09; 第二格&#xff1a;类的属性&#xff08;成员变量&#xff0c;可以没有&#xff09; 第三格&#xff1a;类的操作&…...

PostgreSQL 数据库和 pgAdmin 4

PostgreSQL 数据库和 pgAdmin 4PostgreSQLPostgreSQL 数据库安装PostgreSQL 数据库安装 (Ubuntu)PostgreSQL 数据库其他系统安装PostgreSQL 数据库快速使用入门登录数据库访问数据库参考pgAdmin 4pgAdmin 4 安装使用 pgAdmin 4 登录数据库参考PostgreSQL PostgreSQL 数据库安装…...

quarkus 搭建与基础开发环境配置总结

quarkus搭建与基础开发环境配置总结 大纲 基础概念quarkus2.13.7脚手架工程配置配置maven3.8.7quarkus快速启动quarkus的三种打包方式quarkus将程序打包为二进制文件window环境下quarkus云原生二进制文件打包环境搭建使用GraalVM-java11替换本地java8运行二进制文件 基础概念…...

扩散模型DDPM开源代码的剖析【对应公式与作者给的开源项目,diffusion model】

扩散模型DDPM开源代码的剖析【对应公式与作者给的开源项目&#xff0c;diffusion model】一、简介二、扩散过程&#xff1a;输入是x_0和时刻num_steps&#xff0c;输出是x_t三、逆扩散过程&#xff1a;输入x_t&#xff0c;不断采样最终输出x_0四、具体参考算法流程图五、模型mo…...

变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析

一、变量声明设计&#xff1a;let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性&#xff0c;这种设计体现了语言的核心哲学。以下是深度解析&#xff1a; 1.1 设计理念剖析 安全优先原则&#xff1a;默认不可变强制开发者明确声明意图 let x 5; …...

【网络】每天掌握一个Linux命令 - iftop

在Linux系统中&#xff0c;iftop是网络管理的得力助手&#xff0c;能实时监控网络流量、连接情况等&#xff0c;帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘

美国西海岸的夏天&#xff0c;再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至&#xff0c;这不仅是开发者的盛宴&#xff0c;更是全球数亿苹果用户翘首以盼的科技春晚。今年&#xff0c;苹果依旧为我们带来了全家桶式的系统更新&#xff0c;包括 iOS 26、iPadOS 26…...

<6>-MySQL表的增删查改

目录 一&#xff0c;create&#xff08;创建表&#xff09; 二&#xff0c;retrieve&#xff08;查询表&#xff09; 1&#xff0c;select列 2&#xff0c;where条件 三&#xff0c;update&#xff08;更新表&#xff09; 四&#xff0c;delete&#xff08;删除表&#xf…...

Zustand 状态管理库:极简而强大的解决方案

Zustand 是一个轻量级、快速和可扩展的状态管理库&#xff0c;特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...

黑马Mybatis

Mybatis 表现层&#xff1a;页面展示 业务层&#xff1a;逻辑处理 持久层&#xff1a;持久数据化保存 在这里插入图片描述 Mybatis快速入门 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/6501c2109c4442118ceb6014725e48e4.png //logback.xml <?xml ver…...

中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试

作者&#xff1a;Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位&#xff1a;中南大学地球科学与信息物理学院论文标题&#xff1a;BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接&#xff1a;https://arxiv.…...

Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)

概述 在 Swift 开发语言中&#xff0c;各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过&#xff0c;在涉及到多个子类派生于基类进行多态模拟的场景下&#xff0c;…...

【网络安全产品大调研系列】2. 体验漏洞扫描

前言 2023 年漏洞扫描服务市场规模预计为 3.06&#xff08;十亿美元&#xff09;。漏洞扫描服务市场行业预计将从 2024 年的 3.48&#xff08;十亿美元&#xff09;增长到 2032 年的 9.54&#xff08;十亿美元&#xff09;。预测期内漏洞扫描服务市场 CAGR&#xff08;增长率&…...

测试markdown--肇兴

day1&#xff1a; 1、去程&#xff1a;7:04 --11:32高铁 高铁右转上售票大厅2楼&#xff0c;穿过候车厅下一楼&#xff0c;上大巴车 &#xffe5;10/人 **2、到达&#xff1a;**12点多到达寨子&#xff0c;买门票&#xff0c;美团/抖音&#xff1a;&#xffe5;78人 3、中饭&a…...