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

linux下共享内存的3种使用方式

进程是资源封装的单位,内存就是进程所封装的资源的一种。一般情况下,进程间的内存是相互隔离的,也就是说一个进程不能访问另一个进程的内存。如果一个进程想要访问另一个进程的内存,那么必须要进过内核这个桥梁,这就是共享内存。

在linux中,共享内存有3种方式,分别是POSIX接口,mmap以及system V风格的接口。本文分别介绍这3种共享内存的使用方式。在3种方式中,POSIX接口简洁易用,是最常使用的;system V易用性不是很好,很少使用。

另外,在工作中,共享内存作为一种进程间通信的方式,我们很轻易就能想到它的优点:减少拷贝次数。但是一项技术有优点,同时也必然有局限性,共享内存的局限性就是,在使用的时候往往需要在进程间做同步,进程间同步也会带来性能上的损耗。

1POSIX

1.1/dev/shm

POSIX共享内存接口使用了/dev/shm临时文件系统。

在介绍POSIX共享内存接口之前,有必要先了解linux下的临时文件系统/dev/shm,从名字也能看出来,这个文件系统是专门用作共享内存的。从mount显示的信息中可以看出来,临时文件系统tmpfs被mount到了/dev/shm下。

这是一个临时文件系统,同时也是一个内存文件系统,也就是说在这个文件系统上创建的文件,都是保存在内存中的,而不是保存在磁盘上。可想而知,性能会比较高。可以像普通文件系统一样使用临时文件系统,打开、读写、关闭、删除、拷贝等操作和普通文件是一样的。但是要注意,临时文件系统是保存在内存中的,机器重启之后不再存在。

/dev/shm默认大小是机器物理内存的一半:

使用df -i /dev/shm可以查看默认的inode个数:

修改大小和inode个数,比如我想将大小修改为4G,inode个数修改为1000,使用如下命令进行修改,可以看到,修改是生效的。

1.2example

如下例子是linux文档中的例子,通过man shm_open可以看到这个例子。从例子的实现可以看出来,共享内存依赖项有两个:一个是/dev/shm临时文件系统,一个是mmap。mmap本身就是一种共享内存的方式。所以说POSIX共享内存和mmap并不是完全割裂的,前者依赖于后者。其实我们也可以完全不使用shm_open、shm_unlink接口,而是直接使用mmap,在/dev/shm下创建共享内存也是可以的。

pshm_ucase_bounce.c和pshm_ucase_send.c中分别创建共享内存,大小是struct shmbuf的大小。后者向内存中写hello,前者将hello改成大写的,然后后者打印数据。

pshm_ucase.h:

#include <sys/mman.h>
#include <fcntl.h>
#include <semaphore.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \} while (0)#define BUF_SIZE 1024   /* Maximum size for exchanged string *//* Define a structure that will be imposed on the sharedmemory object */struct shmbuf {sem_t  sem1;            /* POSIX unnamed semaphore */sem_t  sem2;            /* POSIX unnamed semaphore */size_t cnt;             /* Number of bytes used in 'buf' */char   buf[BUF_SIZE];   /* Data being transferred */
};

pshm_ucase_bounce.c:

#include <ctype.h>
#include "pshm_ucase.h"int
main(int argc, char *argv[])
{if (argc != 2) {fprintf(stderr, "Usage: %s /shm-path\n", argv[0]);exit(EXIT_FAILURE);}char *shmpath = argv[1];/* Create shared memory object and set its size to the sizeof our structure */printf("size:%d\n", sizeof(struct shmbuf));int fd = shm_open(shmpath, O_CREAT | O_EXCL | O_RDWR,S_IRUSR | S_IWUSR);if (fd == -1)errExit("shm_open");if (ftruncate(fd, sizeof(struct shmbuf)) == -1)errExit("ftruncate");/* Map the object into the caller's address space */struct shmbuf *shmp = mmap(NULL, sizeof(*shmp),PROT_READ | PROT_WRITE,MAP_SHARED, fd, 0);if (shmp == MAP_FAILED)errExit("mmap");/* Initialize semaphores as process-shared, with value 0 */if (sem_init(&shmp->sem1, 1, 0) == -1)errExit("sem_init-sem1");if (sem_init(&shmp->sem2, 1, 0) == -1)errExit("sem_init-sem2");/* Wait for 'sem1' to be posted by peer before touchingshared memory */if (sem_wait(&shmp->sem1) == -1)errExit("sem_wait");/* Convert data in shared memory into upper case */for (int j = 0; j < shmp->cnt; j++)shmp->buf[j] = toupper((unsigned char) shmp->buf[j]);/* Post 'sem2' to tell the to tell peer that it can nowaccess the modified data in shared memory */if (sem_post(&shmp->sem2) == -1)errExit("sem_post");/* Unlink the shared memory object. Even if the peer processis still using the object, this is okay. The object willbe removed only after all open references are closed. */shm_unlink(shmpath);exit(EXIT_SUCCESS);
}

pshm_ucase_send.c:

#include <string.h>
#include "pshm_ucase.h"int
main(int argc, char *argv[])
{if (argc != 3) {fprintf(stderr, "Usage: %s /shm-path string\n", argv[0]);exit(EXIT_FAILURE);}char *shmpath = argv[1];char *string = argv[2];size_t len = strlen(string);if (len > BUF_SIZE) {fprintf(stderr, "String is too long\n");exit(EXIT_FAILURE);}/* Open the existing shared memory object and map itinto the caller's address space */int fd = shm_open(shmpath, O_RDWR, 0);if (fd == -1)errExit("shm_open");struct shmbuf *shmp = mmap(NULL, sizeof(*shmp),PROT_READ | PROT_WRITE,MAP_SHARED, fd, 0);if (shmp == MAP_FAILED)errExit("mmap");/* Copy data into the shared memory object */shmp->cnt = len;memcpy(&shmp->buf, string, len);/* Tell peer that it can now access shared memory */if (sem_post(&shmp->sem1) == -1)errExit("sem_post");/* Wait until peer says that it has finished accessingthe shared memory */if (sem_wait(&shmp->sem2) == -1)errExit("sem_wait");/* Write modified data in shared memory to standard output */write(STDOUT_FILENO, &shmp->buf, len);write(STDOUT_FILENO, "\n", 1);exit(EXIT_SUCCESS);
}

2mmap

mmap在linux中是经常使用的,mmap不仅仅可以用来共享内存,当我们使用malloc申请内存时,默认情况下如果申请的内存大于128K,那么底层便会使用mmap来从系统申请内存;mmap同样也可以将系统的设备内存映射到用户态。

void *mmap(void *addr, size_t length, int prot, int flags,
                  int fd, off_t offset);

MAP_SHARED共享,一个进程修改之后,另一个进程能看到。如果我们要使用共享内存,那么需要设置这个标志
MAP_PRIVATE

私有,也就是不共享,即使两个进程使用mmap映射的是同一个文件,偏移量都是一样的,那么一个进程的修改,另一个进程也看不到。

从下边的注释可以看到,设置MAP_PRIVATE,使用copy on write机制,当一个进程要写的时候,进程内会拷贝一份。另外,私有的情况下,数据最终会不会保存到文件中,是不确定的。

MAP_ANONYMOUS匿名映射,mmap的倒数第二个参数是一个fd,如果要进行文件映射,那么需要首先打开这个文件,再使用mmap进行映射;如果是匿名映射,那么就不需要指定fd,将fd设置为-1即可。

匿名映射常常用于父子进程间的内存共享,文件映射常常用于没有父子关系的进程间的内存共享。 如果我们想要内存中的内容会保存到一个文件中,并且开机之后还能够使用,那么就必须使用共享和文件映射的方式。

2.1文件映射

文件映射就是要基于文件系统的一个文件来映射,第一节中的POSIX接口,就是使用的临时文件系统中的文件进行映射。如下两个文件aa.c和bb.c,编译之后先运行aa,再运行bb,可以看到aa写的数据,bb能够读到;bb写的数据,aa也能读到。

aa.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>int main() {const char *filename = "./shared_file.txt";const size_t length = 100;int fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, 0666);ftruncate(fd, length);char *shared_mem = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);close(fd);if (shared_mem == MAP_FAILED) {perror("mmap");return 1;}strcpy(shared_mem, "Hello from aa!");printf("after aa write\n");sleep(5);printf("aa read:%s\n", shared_mem);munmap(shared_mem, length);return 0;
}

bb.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>int main() {const char *filename = "./shared_file.txt";const size_t length = 100;int fd = open(filename, O_RDWR, 0666);char *shared_mem = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);close(fd);if (shared_mem == MAP_FAILED) {perror("mmap");return 1;}printf("bb read:%s\n", shared_mem);strcpy(shared_mem, "Hello from bb!");munmap(shared_mem, length);return 0;
}

2.2匿名映射

匿名映射可以用于父子进程间的内存共享,如下是一个例子。

①父进程中首先向共享内存中写"Hello from parent!"

②父进程调用fork创建子进程

③子进程读取内存中的内容,然后向内存中写数据“Hello from child!”

④父进程等待子进程退出,然后读取共享内存中的数据

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>int main() {// 创建匿名共享内存size_t length = 100;char *shared_mem = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);if (shared_mem == MAP_FAILED) {perror("mmap");return 1;}strcpy(shared_mem, "Hello from parent!");pid_t pid = fork();  // 创建子进程if (pid == 0) {// 子进程printf("child read:%s\n", shared_mem);strcpy(shared_mem, "Hello from child!");printf("child write:%s\n", shared_mem);} else {// 父进程wait(NULL);  // 等待子进程结束printf("parent read:%s\n", shared_mem);  // 读取共享内存内容}munmap(shared_mem, length);  // 解除映射return 0;
}

运行结果如下,从结果可以看出来,子进程写的数据,父进程可以读出来,说明内存在父子进程之间是共享的。

如果把mmap中的标志MAP_SHARED改为MAP_PRIVATE,那么内存在父子进程间是不共享的。子进程写数据之后,父进程也看不到,父进程中读出来还是“Hello from parent!”。这个时候内存在父进程和子进程中各有一份。MAP_PRIVATE下,使用写时拷贝的机制,只有写的时候,才会分配一份内存,读的时候不会,所以在子进程中一开始读内存中数据的时候还能看到一开始父进程写的数据。

2.3查看文件映射和匿名映射

/pro/pid/maps中显式了进程的内存映射。

只有在共享映射的时候,才能在maps文件中看到对应的映射,私有映射的时候看不到。

如下是文件映射,可以看到shared_file.txt映射的内存范围。

如下是匿名映射,可以看到匿名映射映射的是/dev/zero。

2.4mmap使用注意问题

2.4.1offset应为PAGE_SIZE的整数倍

mmap的最后一个形参offset,需要是页大小的整数倍,页大小通过sysconf(_SC_PAGE_SIZE)来获取。如果offset不是页的整数倍,那么会返回错误Invalid argument。本人测试,无论是文件映射还是匿名映射,MAP_SHARED还是MAP_PRIVATE,这条限制都存在。

2.4.2文件映射时,映射的内存不能超过文件的大小

当使用文件映射时,如果映射的内存的大小大于文件本身的大小,那么在调用mmap时并不会返回错误,但是在写数据的时候会出现段错误。

如下代码,如果shared_file.txt是不存在的,那么调用open的时候会创建该文件,默认大小是0。不调用ftruncate,直接调用mmap,映射的内存大小是100,这个时候mmap不会返回错误,但是在写内存的时候会出现段错误。所以在使用mmap映射文件的时候,必须要保证offset+length的数据不超过文件的大小才可以。ftruncate可以设置文件的大小。

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>int main() {const char *filename = "./shared_file.txt";const size_t length = 100;int fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, 0666);// ftruncate(fd, length);char *shared_mem = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);close(fd);if (shared_mem == MAP_FAILED) {perror("mmap");return 1;}strcpy(shared_mem, "Hello from aa!");printf("after aa write\n");sleep(5);printf("aa read:%s\n", shared_mem);munmap(shared_mem, length);return 0;
}

3system V

systemV类型的共享内存,要使用4个api。

①首先要使用ftok来获取一个key

②使用shmget获取一个id

③使用shmat获取共享内存的地址

④共享内存使用完毕之后使用shmdt删除共享内存

writer.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <unistd.h>#define SHM_SIZE 100  // 共享内存大小int main() {key_t key = ftok("./shmfile", 65);  // 创建一个唯一的键int shmid = shmget(key, SHM_SIZE, 0666 | IPC_CREAT);  // 获取共享内存段if (shmid < 0) {perror("shmget");return 1;}char *str = (char *)shmat(shmid, NULL, 0);  // 将共享内存附加到当前进程地址空间if (str == (char *)(-1)) {perror("shmat");return 1;}// 向共享内存写入数据strcpy(str, "Hello from writer!");printf("Writer wrote: %s\n", str);sleep(20);shmdt(str);  // 解除共享内存return 0;
}

reader.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <unistd.h>#define SHM_SIZE 100  // 共享内存大小int main() {key_t key = ftok("./shmfile", 65);  // 创建一个唯一的键int shmid = shmget(key, SHM_SIZE, 0666);  // 获取共享内存段if (shmid < 0) {perror("shmget");return 1;}char *str = (char *)shmat(shmid, NULL, 0);  // 将共享内存附加到当前进程地址空间if (str == (char *)(-1)) {perror("shmat");return 1;}// 读取共享内存内容printf("Reader read: %s\n", str);shmdt(str);  // 解除共享内存return 0;
}

如果共享内存只是在父子进程间共享,那么不需要使用ftok通过文件来获取一个key,在使用shmget的时候,直接使用IPC_PRIVATE即可。如下是一个例子,在父子进程间共享内存。

#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <unistd.h>#define SHM_SIZE 100  // 共享内存大小int main() {int shmid = shmget(IPC_PRIVATE, SHM_SIZE, 0666 | IPC_CREAT);  // 获取共享内存段if (shmid < 0) {perror("shmget");exit(1);}// 创建子进程pid_t pid = fork();if (pid < 0) {perror("fork");exit(1);}if (pid > 0) {// 父进程char *str = (char *)shmat(shmid, NULL, 0);  // 将共享内存附加到父进程if (str == (char *)(-1)) {perror("shmat");exit(1);}// 向共享内存写入数据strcpy(str, "Hello from parent!");printf("Parent wrote: %s\n", str);sleep(4);printf("Parent read: %s\n", str);shmdt(str);  // 解除共享内存wait(NULL);  // 等待子进程结束} else {// 子进程sleep(1);  // 等待父进程写入数据char *str = (char *)shmat(shmid, NULL, 0);  // 将共享内存附加到子进程if (str == (char *)(-1)) {perror("shmat");exit(1);}// 读取共享内存内容printf("Child read: %s\n", str);strcpy(str, "Hello from child!");printf("child wrote: %s\n", str);shmdt(str);  // 解除共享内存}// 删除共享内存段if (pid > 0) {shmctl(shmid, IPC_RMID, NULL);  // 只有父进程删除共享内存}return 0;
}

相关文章:

linux下共享内存的3种使用方式

进程是资源封装的单位&#xff0c;内存就是进程所封装的资源的一种。一般情况下&#xff0c;进程间的内存是相互隔离的&#xff0c;也就是说一个进程不能访问另一个进程的内存。如果一个进程想要访问另一个进程的内存&#xff0c;那么必须要进过内核这个桥梁&#xff0c;这就是…...

伊丽莎白·赫莉为杂志拍摄一组素颜写真,庆祝自己荣膺全球最性感女人第一名

语录&#xff1a;女性应该做任何她们想做的事&#xff0c;批评她们的人都见鬼去吧。 伊丽莎白赫莉为《Maxim》杂志拍摄一组素颜写真&#xff0c;庆祝自己荣膺全球最性感女人第一名 伊丽莎白赫莉 (Elizabeth Hurley) 实在是太惊艳了&#xff0c;如今&#xff0c;《马克西姆》杂…...

Qt快捷键说明与用法

编辑与查找 CtrlF&#xff1a;在当前编辑窗口中查找关键字。支持大小写相关、全词匹配、正则表达式匹配等选项&#xff0c;并且查找之后还可以进行替换操作。 CtrlShiftF&#xff1a;进行全局查找&#xff0c;不局限于当前文件。注意&#xff0c;在某些情况下&#xff0c;这个…...

技术周刊 | TS 5.6、Chrome DevTools 性能面板上新、Vite 6 Beta、Fastify v5、HTTP 新方法 Query

增长能力&#xff0c;就是持续做出正确决定的能力。 大家好&#xff0c;我是童欧巴&#xff0c;欢迎来到第 128 期技术周刊。 资讯 TypeScript 5.6 TypeScript 5.6 如期发布。 Chrome DevTools 发布全新性能功能 Chrome DevTools 的性能面板上新测试&#xff0c;包括 Core…...

使用Mockito进行单元测试

1、单元测试介绍 Mockito和Junit是用于单元测试的常用框架。单元测试即&#xff1a;从最小的可测试单元&#xff08;如函数、方法或类&#xff09;开始&#xff0c;确保每个单元都能按预期工作。单元测试是白盒测试的核心部分&#xff0c;它有助于发现单元内部的错误。 单元测试…...

CSS 布局三大样式简单学习

目录 1. css 浮动 1.1 效果1 1.2 效果2 1.3 效果3 1.4 效果4 2. css 定位 2.1 absolute 2.2 relative 2.3 fixed 3. css 盒子模型 3.1 效果1 3.2 效果2 3.3 效果3 3.4 效果4 1. css 浮动 1.1 效果1 1.2 效果2 1.3 效果3 1.4 效果4 2. css 定位 2.1 absolute 2.2 …...

集成运放UA741的原理与应用的探索

我们发现TI公司提供了UA741的内部电路&#xff0c;此电路包括22个晶体管&#xff0c;11个电阻&#xff0c;1个二极管&#xff0c;1个电容。 1UA741设计需求 1.1有短路保护 UA741的短路保护功能‌是指当输出端发生短路时&#xff0c;该器件能够自动保护自身&#xff0c;防止因…...

LeetCode337. 打家劫舍III

// 很好的一道题目&#xff0c;既考察递归又考察动归 // 这个版本超时了&#xff0c;原因是暴搜 // 很显然这里使用的是前序&#xff0c;那是不是应该考虑后序&#xff1f;public int rob(TreeNode root) {if (root null) {return 0;}if (root.left null && root.rig…...

python基础(二) 包和import

包的创建 文件创建命令 在 Django 中&#xff0c;python manage.py startapp first_app 这一行命令的作用是创建一个新的应用&#xff08;app&#xff09;&#xff0c;名为 first_app。在 Django 项目中&#xff0c;"app" 是实现某些功能模块的单独部分&#xff0c…...

选址模型 | 基于混沌模拟退火粒子群优化算法的电动汽车充电站选址与定容(Matlab)

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 基于混沌模拟退火粒子群优化算法的电动汽车充电站选址与定容&#xff08;Matlab&#xff09; 问题建模&#xff1a;首先&#xff0c;需要将电动汽车充电站选址与定容问题进行数学建模&#xff0c;确定目标函数和约束…...

WPF入门教学十 资源与字典

在WPF&#xff08;Windows Presentation Foundation&#xff09;中&#xff0c;资源与字典是用于管理和重用UI元素的重要机制。它们不仅有助于保持XAML代码的整洁&#xff0c;还能提升应用程序的性能和可维护性。以下是关于WPF资源与字典的详细说明&#xff1a; 静态资源与动态…...

Ubuntu20.04配置NVIDIA+CUDA12.2+CUDNN【附所有下载资源】【亲测有效】【非常详细】

Ubuntu20.04 安装 cudatookit 12.2 cudnn 安装_ubuntu安装cuda toolkit-CSDN博客【最新】cuDNN在CUDA11.7Ubuntu20.04下的安装及卸载_cuda11.7对应的cudnn-CSDN博客...

Golang | Leetcode Golang题解之第424题替换后的最长重复字符

题目&#xff1a; 题解&#xff1a; func characterReplacement(s string, k int) int {cnt : [26]int{}maxCnt, left : 0, 0for right, ch : range s {cnt[ch-A]maxCnt max(maxCnt, cnt[ch-A])if right-left1-maxCnt > k {cnt[s[left]-A]--left}}return len(s) - left }f…...

软考高级:系统安全 -区块链特点:去中心化、开放性、自治性、安全性、匿名性

讲解 生活化例子 想象一下&#xff0c;你和朋友们玩一个共享账本的游戏。每个人都可以在账本上记账&#xff0c;没人可以单独改动账本&#xff0c;大家都可以随时查看账本内容&#xff0c;也不用再信任某个单独的人来管理账本。这就类似于区块链的工作原理。 概念讲解 去中…...

Pandas 数据分析入门详解

今日内容大纲介绍 DataFrame读写文件 DataFrame加载部分数据 DataFrame分组聚合计算 DataFrame常用排序方式 1.DataFrame-保存数据到文件 格式 df对象.to_数据格式(路径) ​ # 例如: df.to_csv(data/abc.csv) 代码演示 如要保存的对象是计算的中间结果&#xff0c;或者以…...

【网络】高级IO——epoll版本TCP服务器初阶

目录 前言 一&#xff0c;epoll的三个系统调用接口 1.1.epoll_create函数 1.1.1.epoll_create函数干了什么 1.2. epoll_ctl函数 1.2.1.epoll_ctl函数函数干了什么 1.3.epoll_wait函数 1.3.1.epoll_wait到底干了什么 1.4.epoll的工作过程中内核在干什么 二&#xff0c;…...

xml中的转义字符

文章目录 xml中的转义字符 xml中的转义字符 &amp;对应的字符是& <对应的字符是< >对应的字符是> &quot;对应的字符是" &apos;对应的字符是转义的实体引用虽然简单易用&#xff0c;但是需要记忆&#xff0c;而且如果字符串中包含大量的特殊字…...

Webpack:现代前端项目的强大打包工具

在现代前端开发中&#xff0c;随着应用的复杂性不断提高&#xff0c;我们需要一种工具来管理项目的依赖、优化代码结构并打包资源文件。Webpack 就是这样一个强大的打包工具&#xff0c;它为前端开发者提供了灵活、强大且可扩展的功能。本文将介绍 Webpack 的基本概念、安装与使…...

以root用户登陆ubuntu的桌面环境

去我的个人博客观看&#xff0c;观感更佳哦&#xff0c;&#x1f619;&#x1f619; 前言 在学习Linux的时候&#xff0c;经常都需要使用sudo权限来对配置文件进行修改&#xff0c;常用的方法就是用vim编辑器在命令行界面进行修改&#xff0c;比如sudo vim /etc/profile&#…...

《系统架构设计师教程(第2版)》第17章-通信系统架构设计理论与实践-04-其他网络架构(存储网络架构、软件定义网络架构)

文章目录 1. 存储网络架构1.1 网络连接存储 (NAS)1.2 存储区域网络&#xff08;SAN&#xff09; 2. 软件定义网络架构2.1 软件定义网络&#xff08;SDN&#xff09;2.2 SDN架构2.3 相关技术2.3.1 控制平面技术2.3.2 数据平面技术1&#xff09; 硬件处理方式4&#xff09; 软件处…...

大话Python|基础语法(上)

一、单行注释 以下代码输出一个Hello World&#xff01;字符串 在Python代码中&#xff0c;注释会自动被Python解析器忽略 print(Hello World) 二、多行注释 在Python代码中&#xff0c;注释一共有两种形式&#xff1b; 1、单行注释&#xff1a;注释的内容只有一行 2、多行…...

crosscrossover24支持的游戏有那些

CrossOver刚刚更新了24版本&#xff0c;支持《地平线零之曙光》、《以撒的结合&#xff1a;重生》等游戏。一起来看看它有哪些更新吧&#xff01;之前买过23版的用户可以在1年之内免费升级哦&#xff0c;点击这里查看升级教程。 一、功能优化 - 更新 Wine 至最新的稳定版 Wine …...

如何免费调用GPT API进行自然语言处理

在当今这个信息爆炸的时代&#xff0c;自然语言处理&#xff08;NLP&#xff09;技术正逐步渗透到我们生活的各个方面&#xff0c;从智能客服到内容创作&#xff0c;无一不彰显着其强大的应用价值。而GPT&#xff08;Generative Pre-trained Transformer&#xff09;作为NLP领域…...

vue无感刷新Token并重新请求

vue 拦截器拦截401重新请求Token 无感刷新Token 之后重新请求报401的接口 instance.interceptors.response.use(async (response) > {let { data } response;if (data.code 401 || data.code 403) {return await handleExpiredToken(response.config);}if (data.code ! …...

C++和OpenGL实现3D游戏编程【连载10】——纹理的半透明显示

1、本节实现的内容 上一节课我们讲到了图片的镂空显示,它能在显示图片时去除指定颜色的背景,那么这节课我们来说一下图片的半透明显示效果,半透明效果能给画面带来更高质量的提升,使图片显示的更自然,产生更真实的效果。下面是一个气泡向上漂浮的效果。 气泡效果 2、非纹…...

50页PPT麦肯锡精益运营转型五步法

读者朋友大家好&#xff0c;最近有会员朋友咨询晓雯&#xff0c;需要《 50页PPT麦肯锡精益运营转型五步法》资料&#xff0c;欢迎大家下载学习。 知识星球已上传的资料链接&#xff1a; 企业架构 企业架构 (EA) 设计咨询项目-企业架构治理(EAM)现状诊断 105页PPTHW企业架构设…...

Fyne ( go跨平台GUI )中文文档-小部件 (五)

本文档注意参考官网(developer.fyne.io/) 编写, 只保留基本用法 go代码展示为Go 1.16 及更高版本, ide为goland2021.2 这是一个系列文章&#xff1a; Fyne ( go跨平台GUI )中文文档-入门(一)-CSDN博客 Fyne ( go跨平台GUI )中文文档-Fyne总览(二)-CSDN博客 Fyne ( go跨平台GUI…...

GUI编程19:贪吃蛇小游戏及GUI总结

视频链接&#xff1a;21、贪吃蛇之界面绘制_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1DJ411B75F?p21&vd_sourceb5775c3a4ea16a5306db9c7c1c1486b5 1.游戏中用的的图片素材 1.贪吃蛇游戏的主启动类StartGame&#xff1b; package com.yundait.snake;import j…...

linux StarRocks 安装

一、检查服务器是否支持avx2&#xff0c;如果执行命令显示空&#xff0c;则不支持&#xff0c;那么安装后无法启动BE cat /proc/cpuinfo |grep avx2我的支持显示如下&#xff1a; 二、安装 docker run -p 9030:9030 -p 8030:8030 -p 8040:8040 -p 9001:9000 --privilegedtrue…...

解决RabbitMQ设置x-max-length队列最大长度后不进入死信队列

解决RabbitMQ设置x-max-length队列最大长度后不进入死信队列 问题发现问题解决方法一&#xff1a;只监听死信队列&#xff0c;在死信队列里面处理业务逻辑方法二&#xff1a;修改预取值 问题发现 最近再学习RabbitMQ过程中&#xff0c;看到关于死信队列内容&#xff1a; 来自队…...