【Linux】进程间通信-> 共享内存

共享内存原理

在C语言/C++中,malloc也可以在物理内存申请空间,将申请的物理内存空间通过页表映射到进程地址空间,将内存空间的起始地址(虚拟地址)返回,进而进程可以使用虚拟地址通过页表映射到物理内存的方式访问到申请的内存空间。

那么malloc和共享内存有什么区别呢?

区别就是malloc出来的空间,是进程在自己的堆上申请的空间,没有和其他进程建立映射关系,不能被其他进程共享!

  • 共享内存是用来进程间通信的!
  • 共享内存是一种通信方式,所有想通信的进程都可以使用!
  • OS中一定会可能存在很多的共享内存!

共享内存概念

共享内存是最快的 IPC 方式之一。允许多个进程共享同一块物理内存区域。进程可以通过将共享内存区域映射到自己的地址空间来访问这块内存。这样,多个进程可以直接在共享内存区域中读写数据,大大提高了通信效率。

共享内存函数

shmget函数

  • 创建共享内

成功,返回共享内存的标识符;失败,返回-1。

shmflg

int shmflg 权限标志

常用选项:

  1. IPC_CREAT:如果shm不存在,则创建;存在,则获取。
  2. IPC_EXCL:不能单独使用。

IPC_CREAT | IPC_EXCL:如果shm不存在创建,则创建;存在,则出错返回。(对用户来讲,如果创建成功,一定是一个新的shm)

进程要进行通信,首先要让不同的进程看到同一份资源。如何保证两个进程看到的就是同一份共享内存呢?

key

通过接口int shmget(key_t key,size_t size,int shmflg),第二个参数key来保证。

key_t类型其实就是一个32位的整数。

key是什么不重要,重要的是它能进行唯一性标识一个共享内存段

如何形成一个key呢?通过ftok函数来形成一个key。

ftok函数
  • 将文件路径名和项目标识符转换为System V IPC(进程间通信)key(键)

成功,返回成功创建的key;失败,返回-1。

如果进行通信的两个进程使用ftok函数,传递的两个参数是一样的,那么形成的key值也是同样的,就能找到同一个共享内存。一个进程通过key进行创建,另一个进程通过key获取。

makefile

.PHONY:all
all:shm_client shm_servershm_client:shm_client.ccg++ -o $@ $^ -std=c++11
shm_server:shm_server.ccg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm -f shm_client shm_server

comm.hpp

#ifndef _COMM_HPP_
#define _COMM_HPP_#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cerrno>
#include <cstring>#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>#define PATHNAME "."
#define PROJ_ID 0x88 // 项目标识符,随便写的。
#define MAX_SIZE 4096 //共享内存大小。//获取key
key_t getKey()
{key_t k = ftok(PATHNAME, PROJ_ID);//使用ftok可以获取同样的一个key!if (k < 0){std::cerr << errno << ":" << strerror(errno) << std::endl;exit(1);}return k;
}#endif

shm_server.cc

#include "comm.hpp"int main()
{key_t k = getKey();printf("0x%x\n", k);return 0;
}

 shm_client.cc

#include "comm.hpp"int main()
{key_t k = getKey();printf("0x%x\n", k);return 0;
}

运行结果

size

共享内存大小一般建议为4KB的整数倍,系统分配共享内存是以4KB大小为单位的!这里我们定义了共享内存大小为4096KB,如果定为,例如:4097KB,那么内核给用户的共享内存大小会自动向上取整,申请4096*2KB。注意:内核给用户的申请的大小,和用户能用的大小是两码事,虽然内核会申请4096*2KB,但是用户依旧只能用4097KB(ipcs -m 查看共享内存大小依旧为4097KB)。

创建共享内存

comm.hpp

//获取key
key_t getKey()
{key_t k = ftok(PATHNAME, PROJ_ID);//使用ftok可以获取同样的一个key!if (k < 0){std::cerr << errno << ":" << strerror(errno) << std::endl;exit(1);}return k;
}
int getShmHelper(key_t k, int flags)
{int shmid = shmget(k, MAX_SIZE, flags);if (shmid < 0){std::cerr << errno << ":" << strerror(errno) << std::endl;exit(2);}return shmid;
}
//获取共享内存
int getShm(key_t k)
{return getShmHelper(k, IPC_CREAT);
}
//创建共享内存
int createShm(key_t k)
{return getShmHelper(k, IPC_CREAT | IPC_EXCL);
}

shm_server.cc 

#include "comm.hpp"int main()
{key_t k = getKey();printf("key: 0x%x\n", k);int shmid = createShm(k);printf("shmid: %d\n",shmid);return 0;
}

shm_client.cc

#include "comm.hpp"int main()
{key_t k = getKey();printf("key: 0x%x\n", k);int shmid = getShm(k);printf("shmid: %d\n",shmid);return 0;
}

 运行结果

如何理解key和shmid,有什么区别? 

再谈key

基于上述理解,我们认识到:

我们先谈论两个小问题: 

C语言C++ malloc的时候,例如申请1024字节的内存空间,int *p = malloc(size_t 1024),释放free(p),对空间释放不能局部释放,所以free释放的时候要传入所申请空间的起始地址。

问题是怎么知道从p开始释放多少个字节呢?

虽然用户申请了1024个字节,但是OS为了管理用户申请的空间,还会额外申请一些空间,用来记录用户malloc所申请出来的内存空间的属性信息,例如内存块大小,是否被使用等信息。所以free的时候,会自己去额外申请的空间中搜索内存块大小属性,帮助用户去释放。

在创建进程的时候,OS为了管理进程,以先描述,后组织的方式,还创建了一个描述该进程的内核数据结构PCB,用来记录进程的相关属性,所以OS管理进程就通过PCB来管理。

基于对上面两个问题的理解,那么:

OS中一定会存在很多共享内存,那么OS也要对共享内存以先描述,后组织的方式管理起来!

所以,这里并不单单只是用户申请一块物理内存空间,OS也一定会额外申请一些空间用来记录共享内存的相关属性信息,例如共享内存的权限、容量等。

所以共享内存 = 物理内存块 + 共享内存的相关属性!

实际上,用户在申请共享内存的时候,OS除了给用户申请一个物理内存块之后,还会给共享内存申请一个数据结构对象,OS要管理共享内存,并不是直接通过管理这个物理内存块,而是通过管理这个数据结构对象来进行管理。

创建共享内存的时候,是通过key来保证共享内存在系统中是唯一的。那么key在哪呢?

在创建共享内存时,key就作为共享内存的相关属性被填入到了描述共享内存相关属性的数据结构对象中。

eg:

struct shm{

//相关属性

//...

key_t k;

}

(下面还会谈到)

所以,当我们上层调用ftok创建key,再调用shmget创建共享内存时,把key作为参数传入shmget中,本质是把key设置进创建好的共享内存对应的数据结构对象中某一个属性字段里。另一个进程再去获取共享内存时,并不是通过共享内存对应的物理内存块,而是遍历共享内存所对应的相关属性,查找自己的key和共享内存的key是否相等。

总结:key要通过shmget,设置进共享内存属性中!用来标识该共享内存在内核中的唯一性!

shmid与key类似于文件概念里的fd与inode。使用具有唯一性的key值来标定特定的共享内存,给上层返回一个特定的整数,使用户可以通过这个特定的整数来访问特定的共享内存。

当我们进程退出,再执行的时候,这里报了一个文件已经存在的错误。进程已经结束了,共享内存资源依旧存在。

  • 共享内存生命周期随OS,不是随进程的。除非显式的使用对应的命令或接口释放共享内存,否则共享内存会随着OS的运行一直存在。
  • 生命周期随OS,不随进程,也是所有System V版本进程间通信的共性!
  • 跟管道不一样,管道的生命周期随进程

查看IPC资源

  • ipcs -m/q/s

  • ipcs -m 查看系统中共享内存段的相关信息

删除共享内存

  • 使用命令:ipcrm -m + shmid

  • 删除共享内存也有相关接口,使用shmctl函数。

shmctl函数

  • 控制共享内存

获取共享内存的属性、设置共享内存的属性、删除共享内存都使用shmctl接口。

失败返回-1。

  • shmid:共享内存id。
  • cmd:控制共享内存的方式。

包含选项IPC_STAT、IPC_SET、IPC_RMID、IPC_INFO

其中IPC_RMID选项,就是删除共享内存。

  • buffer:共享内存的相关属性。

comm.hpp

//获取共享内存
//...
//创建共享内存
//...
//删除共享内存
int delShm(int shmid)
{if (shmctl(shmid, IPC_RMID, NULL) == -1){std::cerr << errno << strerror(errno) << std::endl;}
}
//...

shm_server.cc

#include "comm.hpp"int main()
{key_t k = getKey();printf("key: 0x%x\n", k);int shmid = createShm(k);printf("shmid: %d\n", shmid);sleep(5);delShm(shmid);return 0;
}

一般情况,共享内存谁创建谁删除。

至此,我们完成了共享内存的创建与删除工作,但是,目前所创建的共享内存还没有办法使用,因为共享内存还没有和进程关联起来。接下来,我们要完成共享内存与进程关联的工作。

shmat函数

  • 将共享内存段连接到进程的地址空间

成功,返回,共享内存的起始地址;失败,返回-1。

  • shmaddr:指定连接地址。

要将共享内存映射到哪一个地址空间。大部分情况下,这个参数可以不设置,因为我们并不清楚我们想将共享内存映射到虚拟地址空间中的哪个区域。设定位为NULL即可,系统会自动选择一个合适的地址来连接共享内存段。

  • shmflg:标志位,用于控制共享内存段的连接方式和权限等。

常用的标志位为:

SHM_RDONLY:表示以只读方式连接共享内存。如果不设置(0),默认以读写方式连接共享内存段,即进程可以对连接后的共享内存段进行读和写操作。

comm.hpp

//获取共享内存
//...
//创建共享内存
//...
//挂接共享内存
void *attachShm(int shmid)
{void *mem = shmat(shmid, nullptr, 0);if ((long long)mem == -1L) // linux为64位系统,指针大小为8{std::cerr << errno << ": " << strerror(errno) << std::endl;exit(3);}
}
//删除共享内存
//...

 shm_server.cc

#include "comm.hpp"int main()
{key_t k = getKey();printf("key: 0x%x\n", k);int shmid = createShm(k);printf("shmid: %d\n", shmid);char *start = (char*)attachShm(shmid);printf("attch success,address start:%p\n",start);delShm(shmid);return 0;
}

此时,Permission denied 权限被拒绝了。创建共享内存的时候,添加权限即可。

comm.hpp

int getShmHelper(key_t k, int flags)
{int shmid = shmget(k, MAX_SIZE, flags);if (shmid < 0){std::cerr << errno << ":" << strerror(errno) << std::endl;exit(2);}return shmid;
}
//获取共享内存
int getShm(key_t k)
{return getShmHelper(k, IPC_CREAT | 0600);
}
int createShm(key_t k)
{return getShmHelper(k, IPC_CREAT | IPC_EXCL | 0600);
}
//挂接共享内存
void *attachShm(int shmid)
{void *mem = shmat(shmid, nullptr, 0);if ((long long)mem == -1L) // linux为64位系统,指针大小为8{std::cerr << errno << ": " << strerror(errno) << std::endl;exit(3);}
}
//删除共享内存
//...

shm_server.cc

#include "comm.hpp"int main()
{key_t k = getKey();printf("key: 0x%x\n", k);int shmid = createShm(k);printf("shmid: %d\n", shmid);sleep(2);char *start = (char*)attachShm(shmid);printf("attch success,address start:%p\n",start);sleep(2);delShm(shmid);return 0;
}

下面我们写一个监控脚本监测运行一下。 

进程和共享内存挂接工作也已经完成了。接下来就可以进行通信了,当通信结束以后,要先去关联,将进程和共享内存的关联去掉,再删除共享内存。

shmdt函数

  • 去掉进程和共享内存的关联性

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

comm.hpp

//创建共享内存
//...
//挂接共享内存
//...
//共享内存去关联
void detachShm(void *start)
{if (shmdt(start) == -1){std::cerr << errno << ": " << strerror(errno) << std::endl;}
}
//删除共享内存
//...

shm_server.cc 

#include "comm.hpp"int main()
{key_t k = getKey();printf("key: 0x%x\n", k);int shmid = createShm(k);printf("shmid: %d\n", shmid);sleep(2);    char *start = (char*)attachShm(shmid);printf("attch success,address start:%p\n",start);sleep(2);//通信//...//去关联detachShm(start);sleep(2);delShm(shmid);return 0;
}

要通信,让client也和共享内存挂接起来。

 shm_client.cc

#include "comm.hpp"int main()
{key_t k = getKey();printf("key: 0x%x\n", k);int shmid = getShm(k);printf("shmid: %d\n",shmid);sleep(5);    char *start = (char*)attachShm(shmid);printf("attch success,address start:%p\n",start);sleep(5);//通信//...//去关联detachShm(start);sleep(2);return 0;
}

共享内存直接就可以当作缓冲区,可以直接向共享内存输入输出。

共享内存通信

comm.hpp

#ifndef _COMM_HPP_
#define _COMM_HPP_#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cerrno>
#include <cstring>#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>#define PATHNAME "."
#define PROJ_ID 0x66  // 项目标识符,随便写的。
#define MAX_SIZE 4096 // bytekey_t getKey()
{key_t k = ftok(PATHNAME, PROJ_ID); // 使用ftok可以获取同样的一个key!if (k < 0){std::cerr << errno << ":" << strerror(errno) << std::endl;exit(1);}return k;
}
int getShmHelper(key_t k, int flags)
{int shmid = shmget(k, MAX_SIZE, flags);if (shmid < 0){std::cerr << errno << ":" << strerror(errno) << std::endl;exit(2);}return shmid;
}
int getShm(key_t k)
{return getShmHelper(k, IPC_CREAT | 0600);
}
int createShm(key_t k)
{return getShmHelper(k, IPC_CREAT | IPC_EXCL | 0600);
}
void *attachShm(int shmid)
{void *mem = shmat(shmid, nullptr, 0);if ((long long)mem == -1L) // linux为64位系统,指针大小为8{std::cerr << errno << ": " << strerror(errno) << std::endl;exit(3);}
}
void detachShm(void *start)
{if (shmdt(start) == -1){std::cerr << errno << ": " << strerror(errno) << std::endl;}
}
int delShm(int shmid)
{if (shmctl(shmid, IPC_RMID, NULL) == -1){std::cerr << errno << strerror(errno) << std::endl;}
}
#endif

shm_client.cc

#include "comm.hpp"int main()
{key_t k = getKey();printf("key: 0x%x\n", k);int shmid = getShm(k);printf("shmid: %d\n", shmid);sleep(5);char *start = (char *)attachShm(shmid);printf("attch success,address start:%p\n", start);//sleep(5);// 通信const char *message = "Hello server,我是另一个进程,我正在和你通信!";pid_t id = getpid();int cnt = 1;while (true){sleep(1);snprintf(start, MAX_SIZE, "%s[pid:%d][消息编号:%d]", message, id, cnt++);}// 不需要再定义缓冲区,写到共享内存中。共享内存就可以当作用户层缓冲区,可以直接写入。跟管道不同!// char buffer[1024];// while (true)// {//     snprintf(start, sizeof(buffer), "%s[pid:%d][消息编号:%d]", message, id, cnt++);//     memcpy(start, buffer, strlen(buffer) + 1);// }// 去关联detachShm(start);//sleep(2);return 0;
}

 shm_server.cc

#include "comm.hpp"int main()
{key_t k = getKey();printf("key: 0x%x\n", k);int shmid = createShm(k);printf("shmid: %d\n", shmid);// sleep(5);char *start = (char *)attachShm(shmid);printf("attch success,address start:%p\n", start);// sleep(5);// 通信while (true){//共享内存当字符串printf("client say : %s\n", start);sleep(1);}// 管道读取方式:// char buffer[]; read(pipefd, buffer, ...)// 去关联detachShm(start);// sleep(5);delShm(shmid);return 0;
}

至此,就完成了基于共享内存的client、server端的通信。

共享内存特点

  • 优点:所有进程间通信,速度最快!

因为,共享内存被进程共享,当一个进程向共享内存区域写入数据后,其他进程可以立即从该区域读取到最新的数据,能大大的减少数据拷贝次数,无需像其他通信方式那样经过内核空间的中转和数据拷贝,从而大大减少了数据传输的时间开销,提高了通信效率。

同样的代码,使用管道和共享内存的方式通信,考虑键盘输入输出,分别需要几次拷贝?

  • 缺点:没有进行同步与互斥操作,没有对数据做任何保护!

将client端改为每3秒写一次,而此时server读端为每1秒读一次,读比写快:

server端读取一次之后,3秒内一直在重复读取!跟管道不同,管道会阻塞,等待写入。当然,也可以设计出对数据做保护的共享内存,这里就不演示了。

共享内存的数据结构

shmctl可以允许用户调用特定接口获取指定id的共享内存的属性。

其中shmid_ds结构体是OS暴露给用户的一部分用户级数据结构,存放着共享内存的相关属性,也就是上面我们所说的OS为了管理共享内存额外申请空间创建的数据结构对象,当然,此数据结构是用户级的,跟内核中的还有一点差异,不过类似。

上面我们已经知道key是要被设置进共享内存属性中的!在shmid_ds中也可以看到key做了封装,封装到了shm_perm结构体中。

我们也可以获取共享内存的相关属性:

//...    
while (true)
{printf("client say : %s\n", start);struct shmid_ds ds;shmctl(shmid, IPC_STAT, &ds);printf("获取属性:size:%d,pid:%d,myself:%d,key: ", ds.shm_segsz, ds.shm_cpid, getpid(),ds.shm_perm._key);sleep(1);
}
//...

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

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

相关文章

【yolov5】实现FPS游戏人物检测,并定位到矩形框上中部分,实现自瞄

介绍 本人机器学习小白&#xff0c;通过语言大模型百度进行搜索&#xff0c;磕磕绊绊的实现了初步效果&#xff0c;能有一些锁头效果&#xff0c;但识别速度不是非常快&#xff0c;且没有做敌友区分&#xff0c;效果不是非常的理想&#xff0c;但在4399小游戏中爽一下还是可以…

【Maven】Maven打包机制详解

Maven打包的类型&#xff1f; 以下是几种常见的打包形式&#xff1a; 1、jar (Java Archive) 用途&#xff1a;用于包含 Java 类文件和其他资源&#xff08;如属性文件、配置文件等&#xff09;的库项目。特点&#xff1a; 可以被其他项目作为依赖引用。适合创建独立的应用程…

MySQLOCP考试过了,题库很稳,经验分享。

前几天&#xff0c;本人参加了Oracle认证 MySQLOCP工程师认证考试 &#xff0c;先说下考这个证书的初衷&#xff1a; 1、首先本人是从事数据库运维的&#xff0c;今年开始单位逐步要求DBA持证上岗。 2、本人的工作是涉及数据库维护&#xff0c;对这块的内容比较熟悉&#xff…

MySQL数据导出导出的三种办法(1316)

数据导入导出 基本概述 目前常用的有3中数据导入与导出方法&#xff1a; 使用mysqldump工具&#xff1a; 优点&#xff1a; 简单易用&#xff0c;只需一条命令即可完成数据导出。可以导出表结构和数据&#xff0c;方便完整备份。支持过滤条件&#xff0c;可以选择导出部分数据…

Go 协程池 Gopool VS ants 原理解析

写过高并发的都知道&#xff0c;控制协程数量是问题的关键&#xff0c;如何高效利用协程&#xff0c;本文将介绍gopool和ants两个广泛应用的协程池&#xff0c;通过本文你可以了解到&#xff1a; 1. 实现原理 2. 使用方法 3. 区别 背景 虽然通过go func()即可轻量级实现并发&…

无人机无法返航紧急处理方式!

一、检查飞行环境 了解禁飞原因和规定&#xff1a;首先&#xff0c;需要了解所在地区的无人机飞行规定&#xff0c;确认是否存在禁飞区或限飞区。如果处于禁飞区&#xff0c;应遵守相关规定&#xff0c;不要强行飞行。 检查天气情况&#xff1a;恶劣的天气条件&#xff08;如…

NLP论文速读(NeurIPS 2024)|BERT作为生成式上下文学习者BERTs are Generative In-Context Learners

论文速读|BERTs are Generative In-Context Learners 论文信息&#xff1a; 简介&#xff1a; 本文探讨了在自然语言处理&#xff08;NLP&#xff09;领域中&#xff0c;上下文学习&#xff08;in-context learning&#xff09;的能力&#xff0c;这通常与因果语言模型&#x…

vue3<script setup>中使用Swiper

swiper网址 Swiper中文网-轮播图幻灯片js插件,H5页面前端开发 Swiper - The Most Modern Mobile Touch Slider 安装 Swiper npm安装&#xff1a; npm install swiper yarn安装&#xff1a; yarn add swiper 导入带有所有模块&#xff08;捆绑包&#xff09;的 Swiper //…

今日收获(C语言)

一.文件的打开 有这样一个结构体&#xff0c;它内部是文件信息区&#xff0c;文件信息区中的变化可以影响到硬盘中的数据。这个结构体的名字是FILE。我们如果想要写代码对文件进行各种操作&#xff0c;就需要一个指向文件信息区的指针&#xff0c;这个指针的类型是FILE*&#…

node.js卸载并重新安装(超详细图文步骤)

卸载node.js 重新安装nodejs 一、卸载 1、首先进入控制面板卸载程序 2、卸载后 到文件夹中进行进一步的删除 删除上述的几个文件夹 每个人可能不一样&#xff0c;总之是找到自己的nodejs安装路径&#xff0c;下面是我的 ①删除C:UsersAdminAppDataRoaming路径下的npm相关文件…

仓颉编程语言:编程世界的 “文化瑰宝”

我的个人主页 在当今编程领域百花齐放的时代&#xff0c;各种编程语言争奇斗艳&#xff0c;服务于不同的应用场景和开发者群体。然而&#xff0c;有这样一种编程语言&#xff0c;它承载着独特的文化内涵&#xff0c;宛如编程世界里一颗熠熠生辉的“文化瑰宝”&#xff0c;那就…

Android使用JAVA调用JNI原生C++方法

1.native-lib.cpp为要生成so库的源码文件 2.JNI函数声明说明 NewStringUTF函数会返回jstring JNI函数声明规则 3.JAVA中声明及调用JNI函数 声明&#xff1a; 调用

DAY178内网渗透之内网对抗:横向移动篇入口差异切换上线IPC管道ATSC任务Impacket套件UI插件

1.内网横向移动 1、横向移动篇-入口点分析-域内域外打点 2、横向移动篇-IPC利用-连接通讯&计划任务, 3、横向移动篇-IPC利用-命令模式&工具套件 1.1 横向移动入口知识点 收集到域内用户和凭据后&#xff0c;为后续利用各种协议密码喷射通讯上线提供条件&#xff0c;…

宠物行业的出路:在爱与陪伴中寻找增长新机遇

在当下的消费市场中&#xff0c;如果说有什么领域能够逆势而上&#xff0c;宠物行业无疑是一个亮点。当人们越来越注重生活品质和精神寄托时&#xff0c;宠物成为了许多人的重要伴侣。它们不仅仅是家庭的一员&#xff0c;更是情感的寄托和生活的调剂。然而&#xff0c;随着行业…

MySQL数据库——索引结构之B+树

本文先介绍数据结构中树的演化过程&#xff0c;之后介绍为什么MySQL数据库选择了B树作为索引结构。 文章目录 树的演化为什么其他树结构不行&#xff1f;为什么不使用二叉查找树&#xff08;BST&#xff09;&#xff1f;为什么不使用平衡二叉树&#xff08;AVL树&#xff09;&a…

大模型—Ollama 结构化输出

Ollama 结构化输出 Ollama现在支持结构化输出,使得可以按照由JSON模式定义的特定格式来约束模型的输出。Ollama的Python和JavaScript库已经更新,以支持结构化输出。 结构化输出的用例包括: 从文档中解析数据从图像中提取数据结构化所有语言模型响应比JSON模式更可靠和一致开…

欧拉计划 Project Euler 35 题解

欧拉计划 Problem 35 题解 题干思路code暴力筛法rotate函数使用语法示例代码 题干 思路 一个很自然的思路就是暴力找&#xff0c;遍历一百万之内的所有数&#xff0c;也可以先把一百万以内所有的素数筛出来然后从中取选。这里我使用的是暴力算法。 code 暴力 #include <…

pytorch基础之注解的使用--003

Title 1.学习目标2.定义3.使用步骤4.结果 1.学习目标 针对源码中出现一些注解的问题&#xff0c;这里专门写一篇文章进行讲解。包括如何自定义注解&#xff0c;以及注意事项&#xff0c;相信JAVA中很多朋友业写过&#xff0c;但是今天写的是Python哦。。。 2.定义 在 Python…

C#编写的金鱼趣味小应用 - 开源研究系列文章

今天逛网&#xff0c;在GitHub中文网上发现一个源码&#xff0c;里面有这个金鱼小应用&#xff0c;于是就下载下来&#xff0c;根据自己的C#架构模板进行了更改&#xff0c;最终形成了这个例子。 1、 项目目录&#xff1b; 2、 源码介绍&#xff1b; 1) 初始化&#xff1b; 将样…

高效搭建Nacos:实现微服务的服务注册与配置中心

一、关于Nacos 1.1 简介 Nacos&#xff08;Dynamic Naming and Configuration Service&#xff09;是阿里巴巴开源的一款动态服务发现、配置管理和服务管理平台。它旨在帮助开发者更轻松地构建、部署和管理分布式系统&#xff0c;特别是在微服务架构中。Nacos 提供了简单易用…