学习系统编程No.21【进程间通信之共享内存】

news2024/11/17 23:51:51

引言:

北京时间:2023/4/16/21:53,刚刚把新文章发出去,开完班会回来,本来上篇博客在昨天就能发的,昨天下午打了一下午的羽毛球之后,饭都没吃,躺在床上,准备睡觉,定了一个19:30的闹钟,打算起来将博客剩余的内容写完,但是突然收到了一个不好的消息,导致我不能很好的调整自己的心态,但是在目前看来,一切还在可控范围之内,不然我就不会在这码字了,感慨……,导致昨天晚上摆烂了一晚上,好的是今天补了一些,所以一切都还在正轨,So,keep going!这篇博客,我们就真正正式地学习一下有关共享内存的知识
在这里插入图片描述

回顾命名管道

上篇博客,我们学习了有关命名管道和子进程回收等相关知识,知道了进程间通信是不限于"血缘关系"的进程之间的,就算是两个毫不相关的进程之间也具备通信的场景,并且我们知道,我们上篇博客学习的有关命名管道的知识,就是一种用来构建两个毫不相关的进程之间通信的方法,使用命名管道相关的知识,我们就可以让两个没有联系的进程也可以具备通信能力啦!这就是匿名管道和命名管道本质上最大的区别,匿名管道只能让具有"血缘关系"的进程完成进程间通信,而命名管道却不仅可以让具有血缘关系的进程间通信,而且还可以让完全没有联系的进程间进行通信,但匿名管道和命名管道此时除了这个最本质的区别之外,在使用方式上,也还有一定的差别,匿名管道由pipe函数创建并直接打开,而命名管道由mkfifo函数创建后,打开需要使用open系统接口,所以fifo(命名管道)与pipe(匿名管道)除了上述的本质区别之外,最大的区别就是上述使用方式的区别,区分了这两点之后,匿名管道和命名管道在本质上是一样,都只是一份"共享资源"摆了,一份用来存储临时数据的内存级文件而已

共享内存

明白了上述的知识,此时我们就知道了,没有联系的进程之间在使用了命名管道构建了相应环境之后,此时也是可以进行进程间通信的, 本质就是要让两个进程看到同一份共享的资源而已,所以接下来,我们就学习一下,实现两个没有任何联系的进程间完成通信的另一种方法:共享内存

基本原理

如下图所示:

在这里插入图片描述

代码编写

明白了上述的原理之后,此时我们就可以进入第二个阶段,也就是代码编写阶段,自己实现一下使用共享内存完成进程间通信,当然如果想要自己写代码来实现这一功能,那么就必须要使用系统调用接口,因为只有系统调用接口才可以帮助我们在内存中创建共享内存和找到对应创建出来的共享内存,具体接口如下所示:

第一个接口,创建共享内存接口(shmget)
在这里插入图片描述
从上图的使用说明可以看出,该接口头文件:#include <sys/ipc.h> #include <sys/shm.h>,具体使用方式int shmget(key_t key, size_t size, int shmflg);此时通过使用方式可以发现,该接口的第三个参数是一个shmflg表明,这个接口使用的是标志位的传参方式,通过宏定义的方式,实现各种不同的行为,然后通过条件判断和按位与判断是否匹配, 例如上图中的 IPC_CREAT和IPC_EXCL 两个宏,此时这两个宏代与共享内存的创建有关,其中IPC_CREAT允许单独使用,具体的行为就是查看共享内存,如果共享内存不存在,就创建一个共享内存,如果共享内存存在,则获取已经存在的共享内存地址,然后返回,而IPC_EXCL则不允许单独使用,必须和IPC_CREAT配合使用(IPC_CREAT | IPC_EXCL),具体的行为是查看共享内存,如果共享内存不存在,就创建一个共享内存,如果已经存在则立马出错返回,所以得出结论,如果单独使用IPC_CREAT,那么这个共享内存可能是别人正在使用的,是一个旧的共享内存,而如果让IPC_EXCL配合IPC_CREAT,那么此时创建的共享内存一定是一个全新的共享内存,没有被使用的共享内存

搞定了我们之前学习过的有关位图知识的第三个参数,此时我们再来看看其它两个参数,第二个参数显然就是一个表示创建共享内存大小的参数,这里不多做讲解,我们重点看看第一个参数,第一个参数是决定两个进程间能不能找到同一共享内存的关键,表示的是一个关键数字,这个关键数字就是用来让两个进程寻找到同一共享内存的钥匙,类似于就是该共享内存的编号一般!那么这个key值我们应该怎么获取呢?此时就涉及到了第二个系统调用接口,如下:

第二个接口,给共享内存设置关键字(ftok)

在这里插入图片描述

创建一个关键字的系统调用接口,ftok(),具体使用方法如上,头文件:#include<sys/types.h> #include<sys/ipc.h>,调用方式key_t ftok(const char *pathname, int proj_id);具体功能:通过具体参数的传递,创建出一个唯一的 IPC(进程间通信)标识符,为创建的共享内存提供唯一该标识符,进而让两个不同的进程通过该标识符找到对应的共享内存,知道了ftok()接口的主要作用和使用方式,此时要 注意:在操作系统内部,进行进程间通信的不可能只有一对进程,而是有很多很多对进程,所以此时操作系统无论是在效率方面(快速定位对应的共享内存)还是管理方面(不能杂乱无章)都必须对创建出的共享内存进行 管理(先描述,再组织),最终变成对一个一个的struct shm结构体的增删查改,所以此时这个结构体struct shm中存放的就是某一共享内存的全部属性(创建大小、创建时间、对应的key值等),所以通过类比和类推,结合以前的知识,一个文件=内容+属性或者是一个进程=进程对应的内核数据结构(struct task)+ 对应的代码和数据,此时就可以知道在操作系统内部,共享内存=共享内存的内核数据结构(struct shm)+ 在内存上开辟的空间

并且要明白在使用共享内存构建进程间通信场景的时候,和使用命名管道构建进程间通信是类似的,只要一个进程创建了命名管道(也就是打开了某一个文件),此时另一个进程此时就不需要再打开该文件,而是可以直接通过文件名和对应的文件路径,找到对应的文件,然后向该文件中读取或者写入,所以同理,创建共享内存,只要其中一个进程使用shmget()接口创建了共享内存,那么此时另一个进程就不需要再创建了,而只要根据ftok()接口的返回值,找到对应的共享内存(依次比对),然后向其中写入或者读取数据就行了,所以这也就是为什么要使用ftok()接口生成一个key值的主要原因,具体原理如下图所示:

在这里插入图片描述

通过上图和上述文字的描绘,此时我们就明白了使用共享内存进行进程间通信中shmget()接口和ftok()的基本使用原理,shmget()接口就像是盖一个房子,ftok()接口就像是打开该房子某一个房间的钥匙,所以进程间通信的本质还是在构建进程间通信的场景,也就是如何让这两个进程看到同一份"资源"

具体代码如下:

common.hpp文件:

在这里插入图片描述

server.cpp文件:

在这里插入图片描述

client.cpp文件:

在这里插入图片描述

如上图代码所示,在common.hpp文件中对shmget()接口进行封装,一个表示使用该接口创建共享内存,一个表示使用该接口获取共享内存(具体和上述有关key值和IPC_CREAT、IPC_EXCL有关),运行结果如下图所示:

在这里插入图片描述

如上图所示,此时发现当我们使用ftok()接口创建了一个key值时,两个进程由于PATHNAMEPROJ_ID是一样的,所以最终调用ftok()接口生成的返回值是相同的,此时其中一个进程就可以拿着这个key值去创建一个新的共享内存出来,并且另一个进程也可以根据这个key值去寻找对应的共享内存,并且此时还发现,当我们此时先执行client,再执行server,此时的server告诉我们的是该共享内存已经存在,而如果是先执行server,再执行client,此时如下图所示:

在这里插入图片描述

原因很简单,本质上就是IPC_CREATIPC_EXCL的区别,如果先执行client,那此时表示的就是先执行IPC_CREAT,而单独使用IPC_CREAT,如果有对应key值的共享内存,那么就返回该共享内存,如果没有就创建一个新的共享内存,而IPC_CREAT | IPC_EXCL一起使用的时候,如果没有对应key值的共享内存,那么它创建一个新的,如果有对应key值的共享内存,那么由于此时它必须创建一个新的共享内存,所以此时直接就报错,说该共享内存已经存在,同理另一种情况,先执行IPC_CREAT | IPC_EXCL,再执行IPC_CREAT,那么由于使用IPC_CREAT无论是对应key值的共享内存是存在,还是不存在,它都会返回对应key值的共享内存,所以此时可以正常运行
明白了上述知识之后,此时还要明白一点,当我们在某一个程序中使用shmget()接口创建了共享内存之后,当该程序结束,也就是该进程退出,此时共享内存的生命周期并不会像匿名管道和命名管道一样,随着进程的结束而释放,由上图就可以看出,当client进程退出之后,server进程再使用key值匹配共享内存时,显示的是该共享内存已存在,所以得出结论:当创建了一个共享内存之后,如果没有对该共享内存进行删除处理,那么该共享内存就会一直存在,除非电脑被重启

删除共享内存

所以为了解决上述的问题(当进程退出,也就是通信完成,共享内存不会被删除),此时有两个方法,如下:

1.使用指令手动删除

功能指令
显示所有的IPC设施ipcs -a
显示所有的消息队列Message Queueipcs -q
显示所有的信号量ipcs -s
显示所有的共享内存ipcs -m
显示IPC设施的详细信息ipcs -q -i id
删除一个共享内存ipcrm -m shmid

感兴趣的同学可以参考该链接:IPC指令详解

2.使用系统调用接口
接口:shmctl(控制共享内存的状态)

具体使用方式,如下图所示:
在这里插入图片描述
头文件:#include<sys/ipc.h>、#include<sys/shm.h>,具体调用方式: int shmctl(int shmid, int cmd, struct shmid_ds *buf);其中第一个参数shmid表示的就是具体你想要控制的共享内存的shmid;第二个参数cmd表示指定要执行的操作(命令)如:删除或更改共享内存区域的属性,例如:IPC_STAT:获取共享内存的状态信息,并将其存储在由buf指定的shmid_ds结构体中,IPC_SET:设置共享内存区域的状态信息,这些信息包含在buf所指向的shmid_ds结构体中,IPC_RMID:从系统中删除共享内存区域;第三个参数buf:用于传递或接收共享内存的信息,本质就是一个结构体指针,因为共享内存=内核数据结构(struct shmid_ds)+ 开辟的内存,所以此时的buf就是一个指向struct shmid_ds结构体的指针,具体使用方式如下代码所示:
在这里插入图片描述
上述代码表示的就是在程序内部使用系统调用接口,直接删除对应的shmid共享内存

如何给共享内存设置权限:

指令:ipcs -m显示所有所有的共享内存和对应的信息,如下图所示:

在这里插入图片描述
如上图所示,此时我们可以知道,一个共享内存是存在一定的权限的(默认无任何权限),但是如果我们想要让它拥有一定的权限是可以手动添加的,如下代码所示:
在这里插入图片描述

如何关联共享内存

搞定了上述的知识,创建共享内存和释放共享内存,此时我们距离构建出进程间通信就差最后一步了,也就是如何让进程和共享内存可以关联和取消关联,也就是表明,虽然我们自己创建了共享内存,但是这个共享内存并不一定是给我们自己使用的,想要使用该共享内存,就一定要先让该进程和对应的共享内存关联起来,此时就面临一个问题,那应该如何让进程和共享内存关联起来呢?此时就又涉及到了一个系统调用接口:shmat() 具体使用方式如下图所示:
在这里插入图片描述
shmat() 用于将共享内存关联到进程地址空间的函数,其参数具体含义如下:

  1. shmid:需要被关联到进程虚拟地址空间中的共享内存的标识符
  2. shmaddr:指定共享内存被关联到虚拟地址空间的地址,如果为 NULL ,则表示由系统自动分配一个地址
  3. shmflg:指定某进程可以对虚拟地址空间中的共享内存的访问模式、操作标志和权限(如读写、信号量等)

总:shmat() 函数的作用是将 shmid 所对应的共享内存关联到当前进程的地址空间,关联后,该共享内存可以通过 shmaddr 所指定的地址直接访问或者被更新,如果 shmaddr 参数为 NULL ,系统将根据进程地址空间的可用空间自动选择一个合适的地址来映射共享内存,在 shmflg 中可以设置访问权限、创建共享内存等选项,最后,当成功调用 shmat() 函数后,将返回一个指向共享内存区域附加地址的指针,如果发生错误,则返回 -1

注意: 因为共享内存的大小我是知道的,所以此时只要知道共享内存在虚拟地址空间中的起始地址,此时根据起始地址和偏移量(大小),此时就可以获取到整个映射在虚拟地址空间上的共享内存

代码实现如下:
在这里插入图片描述
所以此时只要让两个不同的进程都调用该函数,此时就可以让两个不同的进程都和对应的共享内存关联起来了,此时我们就完成了我们的目的:让两个不同的进程看到同一份"资源",这样我们就将进程间通信的环境给构建出来了,最后,就可以进行进程间通信了,但是,此时要 注意:就是当两个进程之间完成了通信之后,此时要让对应的进程从对应的共享内存中脱离出来(也就是取消关联),此时就又涉及到了一个系统调用接口:shmdt,具体使用方式和shmat上相同,只是参数的传递不同而已,具体调用方式:int shmdt(const void *shmaddr);所以想要让一个进程脱离某个共享内存,此时只需要改变对应进程虚拟地址空间中的共享内存和物理内存上的共享内存的页表映射关系就行,总之,shmdt() 函数可以将对共享内存的访问从当前进程的地址空间中分离出来,并使该共享内存可以被其他进程访问或删除

具体代码如下

具体通信还没开始,但是进程间通信的环境已经搭建完成

客户端:

#include <iostream>
#include "common.hpp"

using namespace std;

int main()
{
    // 1.获取相同的key值
    key_t k = get_key();
    cout << "server key:" << to_hex(k) << endl;
    // 2.创建共享内存(一个创建,一个获取)
    int shmid = creat_shm(k, shmsize);
    cout << "server shmid:" << shmid << endl;
    // 3.将自己和共享内存关联起来(也就是将共享内存关联到进程的虚拟地址空间中)
    char* start = attch_shm(shmid);
    //具体通信内容

    sleep(5);
    // 4.将自己和共享内存取消关联
    detattch_shm(start);

    // struct shmid_ds ds;
    // int n = shmctl(shmid,IPC_STAT,&ds);
    // if(n == -1)
    // {
    //    cout << "key:" << to_hex(ds.shm_perm.__key) << endl;
    //    cout << "creater pid:" << ds.shm_cpid << " : " << getpid() << endl;
    // }

    // 要明白shmget()接口的返回值代表的就是对应共享内存的标识符
    // 4.删除共享内存(删一次就够了)
    del_shm(shmid);//使用shmctl共享内存控制接口,删除一个共享内存

    return 0;
}


服务端:

#include <iostream>
#include "common.hpp"

using namespace std;

int main()
{
    // 1.获取到相同的key值
    key_t k = get_key(); // 这个写法不好,写成获取16进制的方法更好
    cout << "client key:" << to_hex(k) << endl;
    // 2.获取共享内存
    int shmid = get_shm(k, shmsize);
    cout << "client shmid:" << shmid << endl;
    // 3.将自己和共享内存关联起来
    char* start = attch_shm(shmid);
    //具体通信内容
    
    // 4.将自己和共享内存取消关联
    detattch_shm(start);

    return 0;
}



共享代码:

#ifndef __COMM_HPP__
#define __COMM_HPP__

#include <iostream>
#include <sys/ipc.h> //这个是ftok接口的头文件
#include <sys/shm.h>
#include <sys/types.h>
#include <cerrno>
#include <string.h>
#include <cstdio>
#include <stdlib.h>
#include <unistd.h>
#include <cassert>
#include <sys/stat.h>
#include <sys/shm.h>

using namespace std;

// 并且注意:此时的这个头文件的一个公共头文件,让两个进程文件都可以使用的文件
#define PATHNAME "."  // 此时这个表示的就是定义一个路径,待会供给shmget接口使用(自己指定,只要两个进程相同就行)
#define PROJ_ID 0x666 // 这个也就是自己给的,也是可以随便写的
// 此时只要把这两个参数传递给ftok()接口,此时该接口根据特定的算法就会生成一个唯一的key值
// 这样定义的好处就是可以直接让两个进程使用同一个路径和id
//-----------------------------------------------------------------------------------
const int shmsize = 4096; // 这个表示的就是开辟共享内存的大小(单位:字节)

key_t get_key() // 这个key_t本质就是一个int而已,大佬就是喜欢typedef
{
    key_t k = ftok(PATHNAME, PROJ_ID);
    if (k == -1)
    {
        cout << errno << " : " << strerror(errno) << endl;
        exit(1);
    }
    return k; // 代码走到这里表示的就是ftok()函数生成key值成功,此时就可以把这个key值传给别的进程中的shmget()接口使用了
}

// 将key值搞成16进程(用处不大),但是自己要会写
string to_hex(int x)
{
    char buffer[64];
    snprintf(buffer, sizeof(buffer), "0x%x", x);
    return buffer;
}

//此时下面的写法显得代码冗余,所以改进如下写法
int creat_shm_helper(key_t k,int shmsize,int flag)
{
    int shmid = shmget(k,shmsize,flag);
    if(shmid == -1)
    {
        cerr << errno << " : " << strerror(errno) << endl;
        exit(2);//创建失败就退出
    }
    return shmid;
}

int creat_shm(key_t k, int shmsize) // 此时只是将shmget()接口再进行一次封装而已,这个一定要懂
{
    // int IPC = shmget(k,shmsize,IPC_CREAT | IPC_EXCL);//注意:一定要创建一个全新的出来,因为老的可能别的进程正在使用中
    // if(IPC == -1)
    // {
    //     cerr << errno << " : " << strerror(errno) << endl;
    //     exit(2);//创建失败就退出
    // }
    // return IPC;//程序来到这里表示的就是成功,返回对应的内存标识符就行(这样进程就可以根据标识符,找到同一共享内存啦!)
    umask(0);
    return creat_shm_helper(k,shmsize,IPC_CREAT | IPC_EXCL | 0666);
}

int get_shm(key_t k, int shmsize)
{
    // int IPC = shmget(k,shmsize,IPC_CREAT);//此时表示的是获取一个共享内存,只要把对应的key值给给shmget()接口,此时该接口就会依次去和已经存在的共享内存比较,最终把对应key的共享内存放回给给IPC
    // if(IPC == -1)
    // {
    //     cerr << errno << " : " << strerror(errno) << endl;
    //     exit(2);//创建失败就退出
    // }
    // return IPC;
    return creat_shm_helper(k,shmsize,IPC_CREAT);
}

char* attch_shm(int shmid)
{
    char* start = (char*)shmat(shmid,NULL,0);//这个接口的返回值是一个void*,和malloc是一样的,malloc的返回值也是一个void*,本质就是放回一个地址给你
    return start;
}

void detattch_shm(char* start)
{
    int n = shmdt(start);
    assert(n != -1);
}

void del_shm(int shmid)
{
    int n = shmctl(shmid,IPC_RMID,0);
    assert(n != -1);
}


#endif



北京时间:2023/4/20/0:01
在这里插入图片描述

总结:进程间通信之共享内存的知识算是学完了,发现,不管是那种通信方式,本质上还是让两个进程看到同一份"资源"而已!

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

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

相关文章

Spring五大类注解 || Bean的更简单存储

目录 前言&#xff1a; 五大类注解 Controller Service Repository Component Configuration JavaEE标准分层 阿里分层结构 BeanName命名规则 方法注解 Bean 注入方式取Bean 属性注入 Setter注入 构造方法注入 Resource 前言&#xff1a; 使用Spring容器&…

Linux-驱动开发-基础温习

一、裸机开发和驱动开发的区别&#xff1a; 裸机开发&#xff1a;底层&#xff08;相对于linux来说&#xff09;&#xff0c;库 二、linux驱动开发-根据各种框架进行开发 1、 外设比较多&#xff0c;资源多&#xff0c;资料非常少&#xff0c;官方的SDK;直接操作寄存器不显示…

Python 基础(十):元组

❤️ 博客主页&#xff1a;水滴技术 &#x1f338; 订阅专栏&#xff1a;Python 入门核心技术 &#x1f680; 支持水滴&#xff1a;点赞&#x1f44d; 收藏⭐ 留言&#x1f4ac; 文章目录 一、声明元组二、访问元组三、修改元组变量四、遍历元组五、切片六、常用函数和方法6.…

SpringBoot实现导出Excel功能

1 问题背景 需求要做一个导出excel的功能 2 前言 本篇着重阐述后端怎么实现&#xff0c;前端实现的部分只会粗略阐述。该实现方案是经过生产环境考验的&#xff0c;不是那些拿来练手的小demo。本文阐述的方案可以借鉴用来做毕设或者加到自己玩的项目中去。 3 实现思路 后端查询…

103. 二叉树的锯齿形层序遍历【191】

难度等级&#xff1a;中等 上一篇算法&#xff1a; 104. 二叉树的最大深度【75】 力扣此题地址&#xff1a; 103. 二叉树的锯齿形层序遍历 - 力扣&#xff08;Leetcode&#xff09; 1.题目&#xff1a;103. 二叉树的锯齿形层序遍历 给你二叉树的根节点 root &#xff0c;返回其…

p65 内网安全-域环境工作组局域网探针方案

数据来源 基本概念 DMZ区域&#xff1a;称为“隔离区”&#xff0c;也称‘’非军事化区/停火区” 工作组&#xff08;Work Group&#xff09;是局域网中的一个概念。它是最常见最简单最普通的资源管理模式&#xff0c;就是将不同的电脑按功能分别列入不同的组中&#xff0c;以…

完美解决丨except NameError:

示例如下&#xff1a; try: print(xx) except: print(xx is not defined) print(continue) 解决办法 第一种解决办法&#xff1a; try: print(xx) except NameError: print(xx is not defined) print(continue) 第二种解决办法&#xff1a; print(xx) if xx in locals() e…

camunda工作流user task如何使用

在Camunda中使用User Task通常需要以下步骤&#xff1a; 1、创建User Task&#xff1a;使用BPMN 2.0图形化设计器&#xff08;如Camunda Modeler&#xff09;&#xff0c;将User Task元素拖到流程图中&#xff0c;并为任务命名&#xff0c;指定参与者&#xff08;用户或用户组…

第二章 Maven 核心程序解压和配置

第一节 Maven核心程序解压与配置 1、Maven 官网地址 首页&#xff1a; Maven – Welcome to Apache Maven(opens new window) 下载页面&#xff1a; Maven – Download Apache Maven(opens new window) 下载链接&#xff1a; 具体下载地址&#xff1a;https://dlcdn.apac…

算法:(力扣)(牛客)打印螺旋矩阵题

手撕螺旋矩阵 题目思路解题 题目 描述&#xff1a;给定一个m x n大小的矩阵&#xff08;m行&#xff0c;n列&#xff09;&#xff0c;按螺旋的顺序返回矩阵中的所有元素。数据范围&#xff1a;0 \le n,m \le 100≤n,m≤10&#xff0c;矩阵中任意元素都满足 |val| \le 100∣val…

makefile 规则的覆盖

makefile 中经常会使用规则的覆盖&#xff0c;同样一个target 可能有多个prerequisites&#xff0c;这种依赖关系可以放到一起&#xff0c;也可以分开指定。 例1&#xff1a; test1:echo "test111"test2:echo "test222"test3:echo "test333"he…

vsync-app 不稳定导致抖动

问题描述&#xff1a;跟对比机器对比uc 浏览器新闻页滑动场景&#xff0c;出现抖动 1、trace 看是没有丢帧&#xff0c;对比看送帧buffer 给到 SF 步调不够一致&#xff0c;从间隔较大的两个 送帧buffer看&#xff0c;发现vsync-app 时间比正常的要长3ms 左右&#xff0c;vsync…

网络交换机端口管理工具

如今&#xff0c;企业或组织级网络使用数百个交换机端口作为其 IT 基础架构的一部分来实现网络连接。这使得交换机端口管理成为日常网络管理任务的一部分。传统上&#xff0c;网络管理员必须依靠手动网络交换机端口管理技术来跟踪交换机及其端口连接状态。这种手动任务弊大于利…

基于斯坦福大学开源,从零搭建chatGPT

下载地址&#xff1a; https://huggingface.co/datasets/togethercomputer/RedPajama-Data-1T 预处理仓库&#xff1a;https://github.com/togethercomputer/RedPajama-Data 复刻ChatGPT&#xff01;斯坦福等开启红睡衣计划&#xff0c;开源1.2万亿token训练集 【新智元导读…

JAVA队列(Queue)用法附实例讲解

队列是什么 队列用于模拟队列这种数据结构&#xff0c;队列通常是指“先进先出”的容器。新元素插入&#xff08;offer&#xff09;到队列的尾部&#xff0c;访问元素&#xff08;poll&#xff09;操作会返回队列头部的元素。通常&#xff0c;队列不允许随机访问队列中的元素 …

【JavaWeb】Servlet(崔老师版)

文章目录 1.概述1.1 JavaWeb三大组件1.2 Servlet作用 2.ServletConfig接口3.Servlet接口3.1 实现Servlet的方式3.2 Servlet生命周期 4.HttpServlet抽象类6.ServletContext5.1 概述5.2 获取ServletContext5.3 JavaWeb四大域对象5.4 获取应用初始化参数5.5 ServletContext获取资源…

【UML建模】时序图(Sequence Diagram)

文章目录 1.概述2.时序图的组成元素2.1.角色&#xff08;Actor&#xff09;2.2.实体和对象2.3.生命周期线&#xff08;Lifeline&#xff09;2.3.1.激活&#xff08;Activation&#xff09;2.3.2.消息&#xff08;Messages&#xff09;2.3.3.组合片段&#xff08;Fragments&…

learn C++ NO.1——命名空间域、输入输出、函数重载

前言 什么是C C&#xff08;c plus plus&#xff09;是一种计算机高级程序设计语言&#xff0c;由C语言扩展升级而产生&#xff0c;最早于1979年由本贾尼斯特劳斯特卢普在AT&T贝尔工作室研发。C语言是结构化和模块化的语言&#xff0c;适合处理较小规模的程序。对于复杂的…

Cache存储系统详解(全相联映射、直接映射、组相联映射、替换策略和性能计算)

1. 存储系统的层次结构 为了解决容量、速度和价格之间的矛盾&#xff0c;把各种不同存储容量&#xff0c;不同存取速度&#xff0c;不同价格的存储器&#xff0c;按照一定的体系结构组织起来&#xff0c;使所存放的程序和数据按层次分布在各存储器中&#xff0c;形成---多层次的…

读书笔记//《数据科学工程实践》

出版时间&#xff1a;2021年11月 副标题&#xff1a;用户行为分析、A/B实验、SQLFlow 作者们是一线互联网企业的数据科学家、数据分析师和算法工程师&#xff0c;主要就职于滴滴、部分就职于腾讯、快手等。 点评&#xff1a;神仙下凡布道。感谢大佬们的分享。本书让我领略了大厂…