【Linux】线程的概念

news2024/11/19 6:26:13

文章目录

  • 📖 前言
  • 1. 线程的引入
    • 1.1 执行流:
    • 1.2 线程的创建:
    • 1.3 线程的等待:
  • 2. 查看线程
    • 2.1 链接线程库:
    • 2.2 ps -aL:
  • 3. 页表的认识
    • 3.1 二级页表:
    • 3.2 页表的实际大小:
  • 4. 再看线程
    • 4.1 线程总结:
    • 4.2 线程的优点:
    • 4.3 线程的缺点:

📖 前言

从本章开始,我们进入Linux系统编程最后一节多线程的学习,本章我们先来简单的认识一下线程。


1. 线程的引入

在我们之前的Linux学习中,学习了进程的相关概念,操作系统内核中的task_struct描述进程,CPU在运行时,会根据时间片轮询调度进程,让每个进程得以推进。

在之前进程地址空间的学习中,我们知道,每个进程的PCB都可以看到一整个进程地址空间,我们以前学的进程是一个PCB对应一个进程地址空间。

而线程我们可以理解为轻量级进程,每一个进程都可以创建多个线程,并行执行不同的代码。

线程 : 进程 = n : 1
在这里插入图片描述
创建的这三个PCB有了属于它们自己的一小份代码和数据。那么我们把这里的其中一个task_ struct对应的占有这个的进程的一小份代码,一小份数据,使用它局部的一部分页表的,这样的执行流task_struct在Linux中叫做线程

  • 不再独立分配独立的地址空间。
  • 不再分配独立页表,而是所有PCB指向同一个地址空间,甚至将来访问同一张页表。

CPU看待进程和线程是一样的,调度的时候都是以task_struct为单位来调度的。

  • TCB(Thread Control Block)
  • PCB(Process Control Block)

Windows中:

  • 真线程的操作系统当中,pcb和tcb非常复杂。
  • 在真正的线程操作系统中,TCB (Thread Control Block)和PCB(Process Control Block)是分开实现的。

Linux中:

  • 进程和线程在概念上没有区分,只有一个叫做执行流!
  • 进程有优先级,线程也有优先级,都要切换,都要上下文保护, 也要找到对应的代码和数据。
  • 无非是,进程的代码和数据多一些,线程的代码和数据少一些,进程做的工作更多,线程少。

Linux的线程是用进程模拟的PCB模拟的,Linux下也有tcb只不过没有为线程单独设计,用的照样是task_struct

Linux没有提供纯纯的创建线程接口,因为底层没有用真线程,用的是进程作为载体去模拟线程。

进程具有独立性是,有自己的资源,地址空间,页表还有该进程加载到内存中的代码和数据。

以前创建进程是创建独立进程,PCB、地址空间和页表是私有的。

创建线程只创建PCB,CPU调度时,只看PCB。

小结:

  1. 在进程内部运行的执行流。
  2. 线程比进程粒度更细,调度成本更低。
  3. 线程是CPU调度的基本单位。

1.1 执行流:

进程和线程在执行流层面是不一样的。

在Linux中,执行流(Execution Flow)是指程序的执行过程中的控制流动。它描述了程序中指令的顺序执行路径,决定了程序的执行顺序。

  • 单执行流进程:单执行流进程是指在计算机系统中,每个进程只有一个执行线程,即同一时间只能执行一个指令或一个操作。
  • 多执行流进程:多执行流进程是指在计算机系统中,一个进程可以同时拥有多个执行线程,即能够同时执行多个指令或多个操作。

fork之后,父子是共享代码的可以通过if else判断,让父子进程执行不同的代码块不同的执行流,可以做到进行对特定资源的划分。

  • 进程:向系统申请资源的基本单位(系统分配)
  • 线程:系统调度的基本单位

进程(Process)和线程(Thread)在执行流层面上是不一样的:

  • 进程(Process):
    • 进程是操作系统中的一个独立执行单位,它具有独立的内存空间、程序代码和执行环境。
    • 每个进程都有自己的执行流,包括程序计数器(Program Counter)和栈,用于存储指令的地址和局部变量等信息。
    • 进程之间相互独立,并且可以通过进程间通信机制进行数据交换。
  • 线程(Thread):
    • 线程是进程内的一个执行单元,一个进程可以包含多个线程。
    • 与进程不同,线程共享同一个进程的地址空间和资源,在同一个进程中的线程之间可以直接访问共享的内存区域和变量,而无需使用进程间通信的机制。
    • 线程之间可以并发执行,共享进程的执行环境,包括打开的文件、信号处理函数、信号屏蔽字等。

本来串行执行的代码,现在在CPU上可以并发或者并行去执行,让代码在一个时间段或者一个时间点同时得以推进,这种解决方案就叫做线程。

再看进程:

  • 进程 = 内核数据结构 + 进程对应的代码和数据。
  • 进程 = 内核视角:承担分配系统资源的基本实体(进程的基座属性)

再说进程就是PCB就不准确了。包括地址空间,页表,包括构建的映射关系,包括在内存中申请的各种代码和数据对应的内存,包括对应的PCB合起来这一堆才叫进程。

进程的最大意义不是被执行而是:向系统申请资源的基本单位!

  • 内部只有一个执行流的进程 —— 单执行流进程
  • 内部有多个执行流的进程 —— 多执行流进程

以前学的都是单执行流,执行流PCB本身也属于进程内部的资源。
线程是调度的基本单位。

进程切换的成本非常的高,但是进程和线程在CPU中看到的是一样的。
进程切换,地址空间,页表,包括曾经的数据基本都要切换。

内部的执行流就可以称之为一个线程,也就是说一个进程内部可以有一个或者多个线程,CPU调度时, 看到的基本单位全部都叫做线程

1.2 线程的创建:

Linux中没有原生创建线程的接口,但是Linux有原生线程库,由应用级程序员帮我们开发出了一批接口, 叫做pthread_create

不是操作系统的接口,叫做原生线程库:

在这里插入图片描述

  • 第一个参数: 是一个输出型参数,在成功创建线程后,这个变量会被用来保留新线程的ID,供后面的操作使用。
  • 第二个参数: 用来设置线程属性的,可以传递一个nullptr指针,表示使用默认线程属性,也可以通过pthread_attr_t类型的变量来设置自定义的属性。
  • 第三个参数: 是一个函数指针,是指向线程运行函数的指针,函数的返回值和参数必须符合线程函数的要求。
  • 第四个参数: 就是第三个参数,函数指针指向的函数的参数。它是一个void类型的指针,可以传递任意类型的数据给线程函数。

注意:

  • 在现在所有主流的Linux版本中,都默认带这个库,是原生的,在操作系统中就存在的。
  • 不是所谓的系统调用接口,是库函数。

创建线程的时候,本质就是让线程执行进程代码的一部分,有一个进程里面有十几个函数,把某一个函数当做该线程的入口函数,让该线程去调度。

  • CPU看到的所有的task_struct都是一个进程。
  • CPU看到的所有的task_struct都是一个执行流(线程)

线程是属于某一个进程的,所以不需要创建新的mm_struct页表映射,但是创建的效率高于创建子进程。创建新线程后(创建新的PCB)只要将task_struct指向所属进程的mm_struct即可。

在进程中,我们谈父子线程,在线程中,我们谈主新线程。

1.3 线程的等待:

在这里插入图片描述
pthread_ join等待线程的理由:

  1. 释放线程资源,前提是线程退出了。
  2. 获取线程对应的退出码。

线程退出的时候,一般必须要进行join,如果不进行join

  • 就会造成类似于进程那样的内存泄漏的问题(没有僵尸线程这样的说法)
  • 线程对应的退出结果暂时不获取

返回值:

在这里插入图片描述

pthread_join第二个参数的理解:

  • 是一个输出型参数,获取新线程退出时的退出码。
  • 进程退出的三种情况:
    • 代码跑完,结果正确。
    • 代码跑完,结果不正确。
    • 异常。
  • 线程也是一样,执行流的退出情况也是上述三种情况。

pthread_join第二个参数为什么是二级指针:

  • 因为是一个输出型参数,要改变指针,就要传指针的地址。

主线程为何没有获取新线程退出时的信号?

  • 线程异常了的话,那么整个进程也就直接退出了。
  • 线程异常 == 进程异常
  • 所以也就是说,一个线程会影响其他线程的运行。
  • 线程的健壮性不如进程。

线程出异常了,不再是线程的问题了,而是进程的问题了。所以pthread_join不需要退出信号。

所以以后考虑线程终止,只考虑正常终止。


2. 查看线程

我们来创建两个线程,来分别查看一下进程和线程:

#include <iostream>
#include <string>
#include <unistd.h>
// #include <pthread.h>
#include <thread> // C++11的线程库

using namespace std;

void* callback1(void* args)
{
    string name = (char*)args;
    while (true)
    {
        cout << name << ": " << ::getpid() << endl;
        sleep(1);
    }
}

void* callback2(void* args)
{
    string name = (char*)args;
    while (true)
    {
        cout << name << ": " << ::getpid() << endl;
        sleep(1);
    }
}

int main()
{
    // std::thread t([](){
    //     while(true)
    //     {
    //         cout << "线程运行起来啦" << endl;
    //         sleep(1);
    //     }
    // });

    // 等待就可以了
    // t.join();

    pthread_t tid1;
    pthread_t tid2;

    pthread_create(&tid1, nullptr, callback1, (void*)"thread 1");
    pthread_create(&tid2, nullptr, callback2, (void*)"thread 2");

    while (true)
    {
        cout << "我是主线程...: " << ::getpid() << endl;
        sleep(1);
    }

    pthread_join(tid1, nullptr);
    pthread_join(tid2, nullptr);

    return 0;
}

2.1 链接线程库:

创建线程后,像之前那样编译源文件是不行的,因为要链接线程库:

在这里插入图片描述
查看链接的库:

在这里插入图片描述
链接动态库不明白的小伙伴看过来:👉动静态库👈

  • pthread库是和Linux强相关的库,原生线程库,在用户层实现的线程实现的一种线程实现接口。

2.2 ps -aL:

首先我们来查看一下进程:

在这里插入图片描述
只看到了一个进程,但是我们有三个执行流在跑,怎么只是看到了一个?

  • 这是因为,三个执行流是三个线程(线程1,线程2,主线程),同属于一个进程。
  • ps axj选项是查进程的所以只能查一个。

查看线程:

在这里插入图片描述

  • 在Linux中,LWP的缩写代表Lightweight Process,它意味着轻量级进程。
  • 如果LWPPID是相等的,那么就是主线程,俗称进程。
  • 三个执行流的PID是一样的,说明是在同一个进程内的三个执行流。

在这里插入图片描述

多个线程谁先运行也不确定,完全是调度器自己决定。
C++11里的多线程和操作系统底层的原生线程库是封装关系。


3. 页表的认识

字符常量不可被修改曾经是怎么加载到内存中的呢?

  • 字符常量不可被修改,修改的话,编译不会报错,但是运行时报错了。
  • 是因为当尝试着去修改时候,页表里有对应的条目,会限制进行读写。

在这里插入图片描述

如果不可被修改,那么曾经是怎样加载到内存里的呢?

  • 内存在任何时候都可以被读取的,只不过是能不能读取的问题。
  • 所以在语言上,经过虚拟地址到物理地址转化的时候,会有个读取权限,如果是正常数据是RW,如果是字符串是R(只读的)。
  • 所以在尝试写入时,直接在页表那一层拦截这个进程。
  • 那么MMU也叫做内存管理单元,这个硬件结合页表中读取的数据,就会发生异常。
  • 操作系统发现并识别这个异常,解释称信号,发送给目标进程,直接终止掉进程了。

语言层有些字符串是常量的,代码是只读属性是如何保证的,根本原因是因为在转化过程中拦截了。

从用户空间到内核空间的映射是由页表来完成的:

  • 页表分为用户级页表和内核级页表。
  • 页表结构都是一样的,所有进程用的都是一套内存管理机制。
  • UK来确认当前指向的内容是内核代码还是用户代码。
  • UK用来区分进程用的是内核级页表还是用户级页表。
  • 每一个虚拟地址都要对应一个物理地址。

页表有多大:

  • 假设页表只有一张,请问有多少条目?
    • 一共有2^32个条目。
  • 保守计算一个条目8Byte,那么整个页表有多大?
    • 2 ^ 32 * 8 Byte = 32 GB
  • 要是真的这样的话,内存早就被页表占满了。

3.1 二级页表:

操作系统通常使用多级页表(Multilevel Page Table)以实现虚拟内存管理:

  • 32位系统中用的是两级页表。

在这里插入图片描述

  • CPU根据指令内部的地址,进行寻址再访问物理内存的时候,CPU里出来的地址是虚拟地址。
  • 虚拟地址在被转化的过程中,不是直接转化的,而是被划分成了10+10+12

文件系统和物理内存进行IO的时候,IO的基本单位默认是4KB

  • 物理内存通常被划分成大小相等的页框(Page Frame)。
  • 页框是物理内存中的最小单位,用于存储数据和指令。
  • 每个页框的大小由系统设计决定,常见的大小包括 4KB、8KB、16KB 等。

4GB物理内存为例,每个页框4KB,那么一共有,4GB / 4KB = 1024 * 1024 = 2^20 个页框。

操作系统要将页框管理起来:

  • 一定是先描述,再组织。
  • struct page的结构体中描述页框。
  • struct page mem[1024 * 1024]中管理。

虚拟地址编译,也划分好了4KB:

  • 数据加载到内存,实际上是程序按照4KB为单位可以整体加载。
  • 当然也可以把程序的一部分以4KB为单位加载到内存当中。

页表中的page起始地址,只记录了某个page,不关心页内细节:

  • 是否命中是以页为单位的
  • 在用虚拟地址找一级页表和二级页表的时候
  • 其中先找的是page,说明在计算机中找内存是以页为单位找的
  • 找到后根据最后12位,找到在页内的偏移量是什么位置

物理内存一般4GB,一个页框是4KB,那么内存一共被划分成了2^20个页框。

虚拟地址后12位:

  • 虚拟地址的后12位,一共有2^12次方个地址。
  • 而一个页框是4KB = 2^12B,所以虚拟地址后12位将一整个页框所有地址全部覆盖了。

页表中的Page帧地址是用于标识物理内存中每个Page框的编号的。

页表只需要映射到page就不需要映射了,拿虚拟地址后12位做偏移量的:

  • 之前讲的映射是有问题的,我们将虚拟地址到物理地址转化是按照字节为单位映射的。
  • 其实只需要找到page这一目就不要再映射了。
  • 最后再拿虚拟地址后12位找偏移量就好了。

用虚拟地址找page,再根据虚拟地址找页内偏移量来找到的。

page命中:

  • 有没有命中,即要访间的空间是否在物理内存里面。
  • 如果没有命中,那么进程就暂时不被调度了。
  • MMU会报错,会触发缺页中断的东西。

所以CPU就找到了对应的数据,然后就读取里面的数据,此时这里的数据就会被CPU再次拿到,CPU做计算等操作,如果还有寻址指令,那就再回过头,再重复刚刚的过程。

这样做的优点:

  • 进程虚拟地址管理和内存管理,通过页表 + page进行解耦
  • 分页机制 + 按需创建页表 = 节省空间
  • 此时页表就被分离了,就可以实现按需创建

3.2 页表的实际大小:

  • 假设一个条目有20Byte,页表最大也就:20B * (2 ^ 32 / 2 ^ 12) = 20B * 2 ^ 20 = 20B * 1M = 20MB

表映射是通过MMU(内存管理单元)来实现的,软(表)硬件(MMU)结合的方式。


4. 再看线程

4.1 线程总结:

  • 使用计算机的时候,所有的行为都会成为进程,人和计算机交互的时候,全都是以进程为载体完成所有的任务的。
  • 进程是承担分配资源的基本实体。
  • 以前讲的进程是:内核数据结构 + 进程的代码和数据。
  • 内核数据结构,包括把代码和数据加载到内存里,本质是申请内存空间。是在做资源准备,真正去执行的是内部的线程。
  • 线程是在进程的地址空间内去运行的,地址空间是进程看待它自己资源的一个统一的视角,进程看待内存等资源是以统一地址空间的方式去看待的。
  • 线程的执行力度比进程更细,调度成本更低,执行的是进程的一部分,访问的是进程的一部分资源,使用的是进程一部分的数据。
  • 调度成本更低,因为在线程切换时,不需要切换页表地址空间,还有CPU中不可显示的寄存器值,只需要将线程需要切换的上下文数据切换就可以,其他的切换成本就很低了。
  • 线程是CPU调度的基本单位。

4.2 线程的优点:

  • 创建一个新线程的代价要比创建一个新进程小得多。
  • 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多。
  • 线程占用的资源要比进程少很多。
  • 能充分利用多处理器的可并行数量。
  • 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务。
  • 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现。
  • I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

4.3 线程的缺点:

性能损失、健壮性降低、缺乏访问控制、编程难度提高。

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

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

相关文章

全面深入理解TCP协议(超详细)

目录 前言 TCP协议格式 确认应答机制(ACK) 理解可靠性 确认应答的机制 16位窗口大小 缓冲区 流量控制 6个标志位 16位紧急指针 ★三次握手&#xff0c;四次挥手 如何理解连接 如何理解三次握手 如何理解四次挥手 TCP可靠性机制 确认应答机制(补充) ​编辑…

70、Spring Data JPA 的 自定义查询(全手动,自己写完整 SQL 语句)

1、方法名关键字查询&#xff08;全自动&#xff0c;既不需要提供sql语句&#xff0c;也不需要提供方法体&#xff09; 2、Query查询&#xff08;半自动&#xff1a;提供 SQL 或 JPQL 查询&#xff09; 3、自定义查询&#xff08;全手动&#xff09; ★ 自定义查询&#xff08…

前端开发中,文本单行或多行溢出使用省略号显示

1.文本单行溢出使用省略号显示 关键代码如下&#xff1a; .box1{width: 200px;height: 30px;line-height: 30px;margin: 0 auto;background-color: rgba(220, 220, 220, 0.751);/* 单行文本超出隐藏 用省略号代替 */white-space: nowrap;overflow: hidden;text-overflow: ellip…

SpringMVC系列(七)之自定义注解

目录 一. Java注解简介 1.1 Java注解分类 1.2 JDK基本注解 Override Deprecated SuppressWarnings 1.3 JDK元注解 从 Java 7 开始&#xff0c;额外添加了 3 个注解: 1.4 自定义注解 如何自定义注解&#xff1f; 二. 自定义注解示例 枚举类&#xff1a; 示例一&…

Echarts 散点图的详细配置过程

文章目录 散点图 简介配置步骤简易示例 散点图 简介 Echarts散点图是一种常用的数据可视化图表类型&#xff0c;用于展示两个或多个维度的数据分布情况。散点图通过在坐标系中绘制数据点的位置来表示数据的关系。 Echarts散点图的特点如下&#xff1a; 二维数据展示&#xff…

JAVA成员变量首字母小写,第二个字母大写报错问题(原因:Lombok与Spring冲突)

1、问题现象&#xff1a; JAVA类里定义成员变量使用首字母小写&#xff0c;第二个字母大写 Getter Setter public class BrandQueryObject extends QueryObject{private String pName; }结果页面报错&#xff0c;无法找到类型为 cn.wolfcode.ssm.query.BrandQueryObject 的对象…

【Linux】常用工具(上)

Linux 常用工具 一、Linux 软件包管理器 yum1. 软件包2. 查看软件包3. 安装/卸载软件4. yum 其他指令的功能 二、Linux 编辑器 - vim 使用1. vim 的基本概念2. vim 的基本操作&#xff08;1&#xff09;光标移动&#xff08;命令模式&#xff09;&#xff08;2&#xff09;光标…

两届 TOKEN 2049 之间,孙宇晨和波场的布局与野心

2022 年在新加坡举办的 TOKEN 2049 大会上&#xff0c;波场TRON创始人、火币全球顾问委员会成员孙宇晨作为特邀嘉宾出席&#xff0c;并曾提出“波场 TRON 下一步的发展目标是成为主流金融机构”的生态愿景&#xff0c;揭示了波场生态的全新发展方向&#xff0c;以及孙宇晨作为区…

企业架构LNMP学习笔记49

Redis数据持久化操作&#xff1a; 数据、持久化&#xff08;数据在服务或者软件重启之后不丢失&#xff09;。 如果数据只存储在内存中&#xff0c;肯定会丢失&#xff0c;实现持久化&#xff0c;就需要把数据存储在磁盘中&#xff08;hdd ssd&#xff09;。 memcached在宕机…

Linux下生成可执行程序的每一步过程以及链接库的初步认识

程序的翻译 程序在形成可执行程序之前都经历过一系列十分复杂的过程&#xff0c;也就是我们程序的翻译&#xff0c;程序的翻译经过以下阶段&#xff1a; 预处理&#xff08;进行宏替换) 编译&#xff08;生成汇编) 汇编&#xff08;生成机器可识别代码&#xff09; 连接&#…

嵌入式C 语言中的三块技术难点

​ C 语言在嵌入式学习中是必备的知识&#xff0c;甚至大部分操作系统都要围绕 C 语言进行&#xff0c;而其中有三块技术难点&#xff0c;几乎是公认级别的“难啃的硬骨头”。 今天就来带你将这三块硬骨头细细拆解开来&#xff0c;一定让你看明白了。 0x01 指针 指针是公认…

Python 人工智能编程指南:基础、库和工具大全解析

Python 已成为人工智能 (AI) 和机器学习领域的通用语言。其广泛的应用、强大的库生态系统和用户友好的语法使其成为人工智能爱好者、数据科学家和研究人员的理想选择。在这份综合指南中&#xff0c;我们将探讨用于 AI 编程的 Python 基础知识&#xff0c;深入研究关键库&#x…

CKA真题分析-2023年度

补充信息 #补全 # apt install bash-completion source <(kubectl completion bash)# kubectl config get-contexts # cat ~/.kube/config |grep current# kubectl config current-context kubectl config use-context复制粘贴 ctrlshiftc ctrlshiftv # edit编辑时只能使…

ashx后台获取GET、POST、JSON方式提交的刷卡信息,并回应驱动读卡器显示文字播报语音

本示例使用设备&#xff1a; RFID网络WIFI无线TCP/UDP/HTTP可编程二次开发读卡器POE供电语音-淘宝网 (taobao.com) <% WebHandler Language"C#" Class"HttpReader" %>using System; using System.Web; using System.IO; using Newtonsoft.Json;publ…

Ubuntu 22.04.3 LTS安装

最近换电脑了&#xff0c;准备重新装一下ubuntu。多年前装过ubuntu很老的版本&#xff0c;现在发现官网最新的LTS版本是 Ubuntu 22.04.3 LTS 版本。那重新装的话&#xff0c;肯定装最新的版本了。这里我记录下自己的安装过程&#xff0c;作为以后的笔记查看。 我的环境&#x…

《C++ primer plus》精炼(OOP部分)——对象和类(4)

“学习是人类进步的阶梯&#xff0c;也是个人成功的基石。” - 罗伯特肯尼迪 文章目录 友元函数利用友元函数重载<<运算符重载部分示例&#xff1a;矢量类 友元函数 先看看在上一章中我们作为例子的代码&#xff1a; class Student{string name;int grade;int operator…

【开发工具】idea 的全局搜索快捷键(Ctrl+shift+F)失效

文章目录 前言1. 取消 输入法的快捷键&#xff08;推荐使用&#xff09;2.更改 idea的快捷键3. 热键占用总结 前言 当你发现在idea 中看到用于全局搜索的快捷键就是 CtrlshiftF&#xff0c;可是怎么按都不管用的时候&#xff0c;你就不要再执着于自己的操作继续狂点电脑按键了…

SAP 自定义搜索帮助创建与使用

如何创建自定义的搜索帮助 1. 进入事务码SE11,自定义一个搜索帮助的名字 2. 维护数据收集的选择方法以及对话行为和参数信息 点击激活&#xff0c;至此&#xff0c;搜索帮助创建完成 3. 可以给数据表中的对应字段添加搜索帮助 SE11进入&#xff0c;输入数据表名&#xff0c;…

PHP 如何创建一个 composer 包 并在 项目中使用自己的 composer sdk 包

第一步创建一个composer SDK项目 创建一个 composer.json文件或使用 命令 &#xff08;如果不清楚怎么弄 直接跳过即可&#xff0c;一般都会默认配置&#xff09; composer init这是生成的composer.json文件 将自己要使用的包添加到 require 中&#xff0c;如果没有require则…

【计算机视觉 | CNN】Image Model Blocks的常见算法介绍合集(四)

文章目录 一、Dilated Bottleneck with Projection Block二、NVAE Generative Residual Cell三、NVAE Encoder Residual Cell四、Bottleneck Transformer Block五、Spatial Feature Transform六、Big-Little Module七、Scale Aggregation Block八、Multiscale Dilated Convolut…