Linux系统编程:通过System V共享内存实现进程间通信

目录

一. 共享内存实现进程间通信的原理

二. 共享内存相关函数

2.1 共享内存的获取 shmget / ftok

2.2 共享内存与进程地址空间相关联 shmat

2.3 取消共享内存与进程地址空间的关联 shmdt

2.4 删除共享内存 shmctl

2.5 通信双方创建共享内存代码

三. 共享内存实现进程间通信

3.1 实现方法及特性

3.2 为共享内存添加访问控制

四. 总结


一. 共享内存实现进程间通信的原理

要实现进程间通信,就必须让相互之间进行通信的进程看到同一份资源(同一块内存空间),如通过管道实现进程间通信,本质就是让两个进程分别以读和写的方式打开同一份管道文件,一个进程向管道中写数据,另一个进程再从管道中将数据读出,这样两个进程就可以看到同一份内存空间,从而实现了进程间通信。

System V共享内存实现进程间通信的方式与管道相同,区别在于管道是基于文件的,而共享内存则是直接申请内存空间,不需用进行文件相关操作通过System V共享内存实现通信的进程,都会使用物理内存中的同一块空间,这一块公共的物理内存空间经过通信双方进程的页表,映射到进程地址空间的共享区,通信双方进程在运行期间,拿到共享区虚拟地址,通过页表映射,就可以看到同一块物理内存,就可以实现进程间通信。

如果操作系统内有多组通过System V共享内存方式相互通信的进程处于运行状态,那么就会存在多组共享内存,操作系统需要对这些共享内存空间进行管理,管理方式为:先通过struct结构体进行描述,再利用特定的数据结构组织

可以这样理解:共享内存 = 共享的物理内存 + 对应的内核级数据结构。

图1.1 共享内存的实现原理

二. 共享内存相关函数

共享内存实现进程间通信的步骤可以总结为:创建共享内存 -> 共享内存与地址空间相关联 -> 通信 -> 共享内存与地址空间解绑 -> 销毁共享内存。

2.1 共享内存的获取 shmget / ftok

shmget函数:获取共享内存

头文件:#include<sys/ipc.h>、#include<sys/shm.h>

函数原型:int shmget(key_t key, size_t size, int shmflg)

函数参数:

        key -- 特定共享内存的标识符

        size -- 共享内存的大小

        shmflg -- 共享内存获取的权限参数

返回值:创建成功返回共享内存的编号(称为shmid),失败返回-1

共享内存标识符key:OS中可能存在多个共享内存,需要保证通信双方看到同一块共享内存,因此,每个共享内存都需要一个特定的key值进行区分,这个key值是多少并不重要,只要保证它在OS中是唯一的即可通信双方进程(Serve && Client)需要约定相同的算法,保证他们可以使用shmget获取到同一块共享内存

ftok函数可以用于获取key值,只要调用ftok的实参相同,就会返回相同的key值。

ftok函数:获取共享内存标识符key

头文件: #include<sys/ipc.h>、#include<sys/types.h>

函数原型:key_t ftok(const char* pathname, int proj_id);

函数参数:

        pathname:项目(文件)路径

        proj_id:项目(文件)的id编号

返回值:成功返回特定的key值,否则返回-1。

共享内存大小size:以字节为单位,建议取页(PAGE:4096bytes)大小的整数倍,因为如果获取共享内存空间的大小不是页大小的整数倍,OS就会向上取整申请到页大小整数倍的内存空间,但是多申请的空间却不能被用户所使用。如,申请4097bytes的共享内存,OS会实际申请2*4096bytes的空间,而能被使用的只有4097bytes,剩下的都浪费掉了。

权限参数shmflg:有IPC_CREAT、IPC_EXCL、共享内存起始权限码、0这几种选项,他们之间通过竖划线 | 隔开,每个选项都有其意义。

  • IPC_CREAT:如果key标识的共享内存存在,就直接将其获取,如果不存在,就创建。
  • IPC_EXCL:单独使用没有任何意义,一般配合IPC_CREAT使用,IPC_CREAT | IPC_EXCL表示如果共享内存不存在就将其创建,如果存在直接报错,这样可以保证获取到的共享内存是一块全新的共享内存。
  • 起始权限码:用户对于这块共享内存的使用权限,如0666就表示拥有者、所属组、其他人就具有读写权限。
  • 0:只能获取已经存在的共享内存,不能创建新的,不存在就报错。

一般而言,通信双方分别以 IPC_CREAT | IPC_EXCL 和 0 的方式获取共享内存,确保一方创建全新的共享内存,另一方只能获取到该共享内存(传0阻断不存在创建新共享内存的可能)。

代码2.1以 IPC_CREAT | IPC_EXCL | 0666 的方式获取共享内存,运行代码,就可以成功获取共享内存,但是当第二次运行代码,却发现运行出错了(见图2.1),这是因为该共享内存再第一次程序运行后被创建,存在于操作系统中,IPC_CREAT | IPC_EXCL获取的共享内存一定是全新的,因此第二次运行程序会失败,删除该共享内存之后才可以再次成功运行。

结论:共享内存的生命周期是随OS内核的,而不是随进程的。

代码2.1:获取共享内存

// common.hpp -- 头文件
#pragma once#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>#define PATH_NAME "."
#define PROJ_ID 0x66
#define SIZE 4096// shmServe.cc -- 客户端代码源文件(用于接收信息)
#include "common.hpp"int main()
{// 获取共享内存key值key_t k = ftok(PATH_NAME, PROJ_ID);   if(k == -1){perror("ftok");exit(1);}// 创建共享内存int shmid = shmget(k, SIZE, IPC_CREAT | IPC_EXCL | 0666);if(shmid == -1){perror("shmget");exit(2);}printf("Serve# 共享内存获取成功,shmid:%d\n", shmid);return 0;
}
图2.1 代码2.1的两次运行结果

这里介绍两条指令,分别用于查看共享内存信息和删除共享内存:

  • ipcs -m 指令:查看系统中所有共享内存的详细信息。
  • ipcrm -m [shmid]:通过指定共享内存的shmid来删除指定的共享内存。

当然,也可以通过代码删除共享内存,本文后面会讲解。

图2.2 通过指令查看共享内存的属性信息和删除共享内存

2.2 共享内存与进程地址空间相关联 shmat

shmat函数:将共享内存关联到进程地址空间

头文件:#include<sys/types.h>、#include<sys/shm.h>

函数原型:void* shmat(int shmid, const void* shmaddr, int shmflg)

函数参数:

        shmid:进行挂接的共享内存的shmid

        shmaddr:指定挂接的虚拟地址(传NULL表示让OS自动选择挂接地址)

        shmflg:挂接权限相关参数

返回值:若成功返回挂接到的虚拟地址,失败返回nullptr

挂接地址shmaddr参数:由于我们并不可知虚拟地址的具体使用情况,所以这个参数基本都是传NULL/nullptr来让OS自动选择虚拟地址进行关联。 

挂接权限shmflg:如果传SHM_RDONLY,这表示对应共享内存空间只有读权限,传其他都是读写权限,一般shmflg都传实参0。

当共享内存与虚拟地址关联期间,使用ipcs -m指令查看共享内存属性信息,nattch就会变为1,如果通信双方都与共享内存进行了关联,那么nattch就是2。

2.3 取消共享内存与进程地址空间的关联 shmdt

shmdt函数:让共享内存与当前进程脱离

头文件:#include<sys/types.h>、#include<sys/shm.h>

函数原型:int shmdt(const char* shmaddr)

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

2.4 删除共享内存 shmctl

通过共享内存控制shmctl函数(共享内存控制函数),可以删除共享内存。

删除共享内存的操作只要通信双方有一方指向即可,否则会造成重复删除。一般而言,读取信息的进程创建新的共享内存,也负责删除共享内存,遵循谁创建、谁删除的原则。

shmctl函数:控制共享内存

头文件:#include<sys/ipc.h>  #include<sys/shm.h>

函数原型:int shmctl(int shmid, int cmd, struct shmid_ds* buf)

函数参数:

        shmid -- 共享内存的shmid

        cmd -- 控制指令,选择操作

        buf -- 指向描述共享内存属性信息的结构体指针

返回值:成功返回非负数,失败返回-1

形参cmd可以选择具体的控制策略:

  • IPC_STAT -- 以buf为输出型参数,获取共享内存的属性信息。
  • IPC_SET -- 设置共享内存的属性为buf指向的内容。
  • IPC_RMID -- 删除共享内存,此时buf传空指针NULL。

2.5 通信双方创建共享内存代码

代码2.2:头文件common.hpp -- 由通信双方共同包含

#pragma once#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>#define PATH_NAME "."
#define PROJ_ID 0x66
#define SIZE 4096

代码2.3:服务端代码shmServe.cc -- 用于数据读取

#include "common.hpp"int main()
{// 获取共享内存key值key_t k = ftok(PATH_NAME, PROJ_ID);   if(k == -1){perror("Serve ftok");exit(1);}printf("Serve# 成功获取key值,key:%d\n", k);// 创建共享内存int shmid = shmget(k, SIZE, IPC_CREAT | IPC_EXCL | 0666);if(shmid == -1){perror("Sreve shmget");exit(2);}printf("Serve# 共享内存获取成功,shmid:%d\n", shmid);// 将共享内存与进程相关联char* shmaddr = (char*)shmat(shmid, NULL, 0);if(shmaddr == nullptr){perror("Serve shmat");exit(3);}printf("Serve# 共享内存与进程成功关联,shmid:%d\n", shmid);// 通信代码// ... ...// 让共享内存脱离当前进程int n = shmdt(shmaddr);if(n == -1){perror("Serve shmdt");exit(4);}printf("Serve# 共享内存成功脱离进程,shmid:%d\n", shmid);// 删除共享内存n = shmctl(shmid, IPC_RMID, NULL);if(n == -1){perror("Serve shmctl");exit(5);}printf("Serve# 共享内存删除成功,shmid:%d\n", shmid);return 0;
}

代码2.4:客户端代码shmClient.cc -- 用于数据发送

#include "common.hpp"int main()
{// 获取共享内存key值key_t k = ftok(PATH_NAME, PROJ_ID);   if(k == -1){perror("Client ftok");exit(1);}printf("Client# 成功获取key值,key:%d\n", k);// 创建共享内存int shmid = shmget(k, SIZE, 0);if(shmid == -1){perror("Client shmget");exit(2);}printf("Client# 共享内存获取成功,shmid:%d\n", shmid);// 将共享内存与进程相关联char* shmaddr = (char*)shmat(shmid, NULL, 0);if(shmaddr == nullptr){perror("Client shmat");exit(3);}printf("Client# 共享内存与进程成功关联,shmid:%d\n", shmid);// 通信代码// ... ...// 让共享内存脱离当前进程int n = shmdt(shmaddr);if(n == -1){perror("Client shmdt");exit(4);}printf("Client# 共享内存成功脱离进程,shmid:%d\n", shmid);return 0;
}

三. 共享内存实现进程间通信

3.1 实现方法及特性

在数据输入端(shmClient),我们可以将共享内存视为一块通过malloc得来的char*指向的一段动态内存空,可以使用printf系列函数向这块空间写数据,或者将共享内存空间视为数组,使用下标的形式给每个位置赋值,这样就实现了将数据写入共享内存。

在数据读取端(shmServe),可以将共享内存视为一个大字符串,通过特定的方式,从这个大字符串中获取数据即可。

代码3.1和代码3.2实现了共享内存进程间通信的简单逻辑,在shmClient端,通过下标访问的方式,每隔3s写一次数据,在shmServe端,每隔1s读取一次数据。先运行shmServe端代码,间隔几秒后运行shmClient端代码,根据图3.1展示的运行结果,shmServe端在shmClient端开始运行之前就开始读取共享内存中的内容,在shmClient运行起来后,由于读快写慢,shmClient写入的内容在shmServe端被多次读取,可见,共享内存,没有访问控制。

结论1:共享内存没有访问控制。

代码3.1:shmClient端发送数据

    // 通信代码char ch = 'a';int count = 0;for(; ch <= 'c'; ++ch){shmaddr[count++] = ch;printf("write succsee# %s\n", shmaddr);sleep(3);}snprintf(shmaddr, SIZE, "quit");

代码3.2:shmServe端读取数据 

    // 通信代码while(true){printf("[Client say]# %s\n", shmaddr);if(strcmp(shmaddr, "quit") == 0) break;sleep(1);}
图3.1 共享内存通信读写双方代码执行结果

通过观察上面的代码我们发现,用户可以直接向共享内存中写数据和从共享内存中读取数据,不需要经过用户级缓冲区,共享内存的读或写操作最少只需要一次拷贝即可完成。而通过管道进行读写,则需要将数据预先写入或读入缓冲区,才可以写入管道文件或读出。图3.2为使用管道和共享内存的方法进行进程间通信时,读和写操作涉及的数据拷贝情况,管道通信至少要进行两次数据拷贝,而共享内存可以只进行一次数据拷贝,因此共享内存是一种高效的进程间通信手段。

结论2:共享内存进行进程间通信,通信的一方向共享内存中写入数据,通信的另一方马上就能读取到数据,不需要向操作系统中拷贝数据,共享内存是所有进程间通信方法中效率最高的。

图3.2 管道和共享内存实现进程间通信的资源拷贝情况

管道通信的特性总结:

  1. 不具有访问控制,存在并发问题。
  2. 不需要向OS内核中拷贝数据,通信效率高。

3.2 为共享内存添加访问控制

通过使用命名管道加以辅助,就可以为共享内存添加访问控制,具体的实现方法和原理为:

  • 在读端(shmServe)程序开始运行时创建命名管道文件,程序运行结束后管道文件销毁。
  • 在写端(shmClient)向共享内存中写入数据后,向管道文件中写入任意的、少量的数据,在读端(shmServe)获取共享内存内容之前,先读取管道中的资源,如果写端没有将期望的数据全部写入共享内存,那么就不会向管道中写数据,读端就必须阻塞等待管道中被写入数据,也就无法获取共享内存中的数据。只有当写端完成向共享内存中写入一次数据,然后向管道文件中写入数据让读端读到了管道资源后,读端代码才可以继续运行,获取到共享内存中的资源。

代码3.3 ~ 3.5,为通过管道为共享内存添加访问控制的实现代码。

代码3.3:common.hpp头文件 -- 被通信双方源文件包含

#pragma once#include <iostream>
#include <cstring>
#include <cassert>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/shm.h>#define PATH_NAME "."
#define PROJ_ID 0x66
#define SIZE 4096#define FIFO_NAME "fifo.ipc"
#define MODE 0666// 定义类,其构造和析构函数可以创建和销毁管道文件
class Init
{
public:Init(){int n = mkfifo(FIFO_NAME, MODE);if(n == -1) perror("mkfifo");assert(n != -1);(void)n;}~Init(){int n = unlink(FIFO_NAME);assert(n != -1);(void)n;}
};#define READ O_RDONLY 
#define WRITE O_WRONLY// 管道文件打开函数
int OpenFifo(const char* pathname, int flags)
{int fd = open(pathname, flags);assert(fd != -1);return fd;
}// 等待函数 -- 用于读端访问控制
// 管道内没有资源时就阻塞
void Wait(int fd)
{uint32_t temp = 0;ssize_t sz = read(fd, &temp, sizeof(uint32_t));assert(sz == sizeof(uint32_t));(void)sz;
}// 唤醒函数 -- 用于写端进程控制
// 向管道内写数据,终止读端进程的阻塞等待
void WakeUp(int fd)
{uint32_t temp = 1;ssize_t sz = write(fd, &temp, sizeof(uint32_t));assert(sz == sizeof(uint32_t));(void)sz;
}// 管道关闭函数
void CloseFifo(int fd)
{close(fd);
}

代码3.4:读端源文件(shmServe.cc)代码

#include "common.hpp"// 全局类对象
// 构造和析构函数分别负责管道文件的创建和销毁
Init init;int main()
{// 获取共享内存key值key_t k = ftok(PATH_NAME, PROJ_ID);   if(k == -1){perror("Serve ftok");exit(1);}printf("Serve# 成功获取key值,key:%d\n", k);// 创建共享内存int shmid = shmget(k, SIZE, IPC_CREAT | IPC_EXCL | 0666);if(shmid == -1){perror("Sreve shmget");exit(2);}printf("Serve# 共享内存获取成功,shmid:%d\n", shmid);// 将共享内存与进程相关联char* shmaddr = (char*)shmat(shmid, NULL, 0);if(shmaddr == nullptr){perror("Serve shmat");exit(3);}printf("Serve# 共享内存与进程成功关联,shmid:%d\n", shmid);// 通信代码int fd = OpenFifo(FIFO_NAME, READ);   // 只读方式打开管道文件while(true){Wait(fd);   // 等待读取printf("[Client say]# %s\n", shmaddr);if(strcmp(shmaddr, "quit") == 0) break;}// while(true)// {//     printf("[Client say]# %s\n", shmaddr);//     if(strcmp(shmaddr, "quit") == 0) break;//     sleep(1);// }// 让共享内存脱离当前进程int n = shmdt(shmaddr);if(n == -1){perror("Serve shmdt");exit(4);}printf("Serve# 共享内存成功脱离进程,shmid:%d\n", shmid);// 删除共享内存n = shmctl(shmid, IPC_RMID, NULL);if(n == -1){perror("Serve shmctl");exit(5);}printf("Serve# 共享内存删除成功,shmid:%d\n", shmid);CloseFifo(fd);return 0;
}

代码3.5:写端源文件(shmClient.cc)代码

#include "common.hpp"int main()
{// 获取共享内存key值key_t k = ftok(PATH_NAME, PROJ_ID);   if(k == -1){perror("Client ftok");exit(1);}printf("Client# 成功获取key值,key:%d\n", k);// 创建共享内存int shmid = shmget(k, SIZE, 0);if(shmid == -1){perror("Client shmget");exit(2);}printf("Client# 共享内存获取成功,shmid:%d\n", shmid);// 将共享内存与进程相关联char* shmaddr = (char*)shmat(shmid, NULL, 0);if(shmaddr == nullptr){perror("Client shmat");exit(3);}printf("Client# 共享内存与进程成功关联,shmid:%d\n", shmid);// 通信代码int fd = OpenFifo(FIFO_NAME, WRITE);while(true){ssize_t sz = read(0, shmaddr, SIZE);    // 共享内存从键盘中读入数据(换行符也被写入)assert(sz >= 0);shmaddr[sz - 1] = '\0';   //末尾添加'\0'表示终止WakeUp(fd);    // 唤醒读端进程if(strcmp(shmaddr, "quit") == 0) break;}// char ch = 'a';// int count = 0;// for(; ch <= 'c'; ++ch)// {//     shmaddr[count++] = ch;//     printf("write succsee# %s\n", shmaddr);//     sleep(3);// }// snprintf(shmaddr, SIZE, "quit");// 让共享内存脱离当前进程int n = shmdt(shmaddr);if(n == -1){perror("Client shmdt");exit(4);}printf("Client# 共享内存成功脱离进程,shmid:%d\n", shmid);CloseFifo(fd);return 0;
}

四. 总结

  • System V共享内存实现进程间通信的底层原理是通信双方进程看到同一块内存,位于物理内存上的共享内存块,通过页表映射到通信双方的进程地址空间的共享区,通信双方拿到共享区的虚拟地址,通过页表映射,访问到同一块物理内存。
  • 使用System V共享内存实现进程间通信的操作流程为:通过ftok函数获取唯一的共享内存标识符key -> 通过shmget函数获取共享内存 -> 通过shmat函数让共享内存和进程绑定 -> 【进行进程通信】-> 通过shmdt函数让共享内存和进程脱离 -> 通过shmctl删除共享内存。
  • System V共享内存 进程间通信的特点为:(1)不需要向操作系统内核中拷贝数据,是所有进程间通信的方法中效率最高的。(2)没有访问控制。
  • 通过管道的辅助,可以为 System V共享内存 进程间通信添加访问控制。

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

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

相关文章

承接各种设计

小弟985研究生毕业&#xff0c;目前攻读读博士&#xff0c;可做各种设计&#xff0c;包括但不限于Matlab 电力电子/电气工程&#xff0c;matlab/simulink 电气专业仿真MATLAB 电气工程专业&#xff0c;matlab建模 电力电子&#xff0c;电气工程&#xff0c;电力系统&#xff0c…

vue echarts macd指标 完整代码

1 逻辑 给指定的series两个对象 两个对象有相同的xAxisIndex: 2,yAxisIndex: 2, 不同的data {name: "",type: "line",data: data1,xAxisIndex: 2,yAxisIndex: 2,},{name: "",type: "bar",data: data2,xAxisIndex: 2,yAxisIndex: 2,},…

Mac M2 Pro安装使用Cocoapods

Mac Pro M2安装使用Cocoapods 在新公司要做iOS开发&#xff0c;所以在新电脑上安装Cocoapods 在升级gem&#xff0c;sudo gem update --system&#xff0c;和安装cocoapods时都遇到如下的提示&#xff1a; ERROR: While executing gem ... (Errno::EPERM)Operation not per…

Linux下安装nodejs

1、下载nodejs 点击前往&#xff1a;Download | Node.js 2、解压 tar -xvf node-v18.16.0-linux-x64.tar.xz mv node-v18.16.0-linux-x64/ /usr/local/nodejs 3、 建立软链接 此时的bin文件夹中已经存在node以及npm&#xff0c;如果你进入到对应文件的中执行命令行一点问题…

现代C++:使用 shared_from_this 防止 this 提前被释放

首先概括一下shared_from_this的作用&#xff1a;可以在类的成员函数中直接通过this得到指向当前所在对象的shared_ptr的智能指针&#xff0c;具体操作如下。 使用方法 设需要提供shared_from_this方法的类为C0定义为类&#xff0c;首先需要将C0定义为 std::enable_shared_fr…

mysql 02 数据库的约束

为防止错误的数据被插入到数据表&#xff0c;MySQL中定义了一些维护数据库完整性的规则&#xff1b;这些规则常称为表的约束。常见约束如下&#xff1a; 主键约束 主键约束即primary key用于唯一的标识表中的每一行。被标识为主键的数据在表中是唯一的且其值不能为空。这点类似…

前后端分离------后端创建笔记(10)用户修改

本文章转载于【SpringBootVue】全网最简单但实用的前后端分离项目实战笔记 - 前端_大菜007的博客-CSDN博客 仅用于学习和讨论&#xff0c;如有侵权请联系 源码&#xff1a;https://gitee.com/green_vegetables/x-admin-project.git 素材&#xff1a;https://pan.baidu.com/s/…

Spring Boot实现第一次启动时自动初始化数据库流程详解

随着互联网的发展项目中的业务功能越来越复杂&#xff0c;有一些基础服务我们不可避免的会去调用一些第三方的接口或者公司内其他项目中提供的服务&#xff0c;但是远程服务的健壮性和网络稳定性都是不可控因素。 在测试阶段可能没有什么异常情况&#xff0c;但上线后可能会出…

设计HTML5文本

网页文本内容丰富、形式多样&#xff0c;通过不同的版式显示在页面中&#xff0c;为用户提供最直接、最丰富的信息。HTML5新增了很多文本标签&#xff0c;它们都有特殊的语义&#xff0c;正确使用这些标签&#xff0c;可以让网页文本更严谨、更符合语义。 1、通用文本 1.1、标…

算法竞赛备赛之搜索与图论训练提升,暑期集训营培训

目录 1.DFS和BFS 1.1.DFS深度优先搜索 1.2.BFS广度优先搜索 2.树与图的遍历&#xff1a;拓扑排序 3.最短路 3.1.迪杰斯特拉算法 3.2.贝尔曼算法 3.3.SPFA算法 3.4.多源汇最短路Floy算法 4.最小生成树 4.1.普利姆算法 4.2.克鲁斯卡尔算法 5.二分图&#xff1a;染色法…

7. CSS(四)

目录 一、浮动 &#xff08;一&#xff09;传统网页布局的三种方式 &#xff08;二&#xff09;标准流&#xff08;普通流/文档流&#xff09; &#xff08;三&#xff09;为什么需要浮动&#xff1f; &#xff08;四&#xff09;什么是浮动 &#xff08;五&#xff09;浮…

OpenAI全球招外包大军,手把手训练ChatGPT取代码农 ; 码农:我自己「杀」自己

目录 前言 OpenAI招了一千多名外包人员&#xff0c;训练AI学会像人类一样一步步思考。如果ChatGPT「学成归来」&#xff0c;码农恐怕真的危了&#xff1f; 码农真的危了&#xff01; 当时OpenAI也说&#xff0c;ChatGPT最合适的定位&#xff0c;应该是编码辅助工具。 用Cha…

计算机竞赛 opencv 图像识别 指纹识别 - python

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于机器视觉的指纹识别系统 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;3分工作量&#xff1a;3分创新点&#xff1a;4分 该项目较为新颖&#xff0c;适…

Vue引入Echarts报错 import * as echarts from “echarts“;

项目场景&#xff1a; 已经下载好echarts cnpm i echarts Vue引入Echarts import echarts from echarts mounted() {this.myChart echarts.init(document.querySelector(.right))this.myChart.setOption({title: {text: 消费列表,left: center},...问题描述 原因分析&#…

【100天精通python】Day38:GUI界面编程_PyQT从入门到实战(中)

目录 专栏导读 4 数据库操作 4.1 连接数据库 4.2 执行 SQL 查询和更新&#xff1a; 4.3 使用模型和视图显示数据 5 多线程编程 5.1 多线程编程的概念和优势 5.2 在 PyQt 中使用多线程 5.3 处理多线程间的同步和通信问题 5.3.1 信号槽机制 5.3.2 线程安全的数据访问 Q…

日常BUG——通过命令行创建vue项目报错

&#x1f61c;作 者&#xff1a;是江迪呀✒️本文关键词&#xff1a;日常BUG、BUG、问题分析☀️每日 一言 &#xff1a;存在错误说明你在进步&#xff01; 一、问题描述 在使用vue命令行创建一个vue项目时&#xff0c;出现一下的错误&#xff1a; vue create my…

UDP数据报结构分析(面试重点)

在传输层中有UDP和TCP两个重要的协议&#xff0c;下面将针对UDP数据报的结构进行分析 UDP结构图示 UDP报头结构的分析 UDP报头有4个属性&#xff0c;分别是源端口&#xff0c;目的端口&#xff0c;UDP报文长度&#xff0c;校验和&#xff0c;它们都占16位2个字节&#xff0c;所…

【java面向对象中static关键字】

提纲 static修饰成员变量static修饰成员变量的应用场景static修饰成员方法static修饰成员方法的应用场景static的注意事项static的应用知识&#xff1a;代码块static的应用知识&#xff1a;单例设计模式 static静态的意思&#xff0c;可以修饰成员变量&#xff0c;成员方法&a…

FPGA_学习_14_第一个自写模块的感悟和ila在线调试教程与技巧(寻找APD的击穿偏压)

前一篇博客我们提到了&#xff0c;如果要使用算法找到Vbr&#xff0c;通过寻找APD采集信号的噪声方差的剧变点去寻找Vbr是一个不错的方式。此功能的第一步是在FPGA中实现方差的计算&#xff0c;这个我们已经在上一篇博客中实现了。 继上一篇博客之后&#xff0c;感觉过了很久了…

【Image captioning】ruotianluo/self-critical.pytorch之1—数据集的加载与使用

【Image captioning】ruotianluo/self-critical.pytorch之1—数据集的加载与使用 作者&#xff1a;安静到无声 个人主页 数据加载程序示意图 使用方法 示例代码 #%%from __future__ import absolute_import from __future__ import division from __future__ import print_…