System V IPC奥秘:解锁共享内存、消息队列与信号量的高效通信之路


目录
- `🍑system V共享内存 `
- `🍒共享内存的原理`
- `共享内存数据结构`
- `查看和删除共享内存资源的命令`
- `🌻共享内存函数`
- `shmget函数`
- `ftok函数`
- `shmat函数`
- `shmdt函数`
- `shmctl函数`
- `🍃共享内存的优缺点`
- `🦔system V消息队列(了解)`
- `消息队列的相关操作接口`(相关接口的参数与共享内存类似)
- `🦅system V信号量 sem`
- `📚信号量理论`
- `信号量的数据结构:`
- `信号量的相关操作接口`
- `指令查找以及删除信号量:`
🍑system V共享内存
共享内存是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到
内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。
🍒共享内存的原理
OS在内存上开辟一段空间,然后与进程构建映射(通过页表),映射到堆栈之间的共享区中,将该空间的起始地址(虚拟地址)返回给用户,该进程就可以通过虚拟地址访问该内存了。
其中,开辟空间和构建映射关系,需要修改进程地址空间和页表,都是内核数据结构,都是OS做的,所以OS会提供相应的系统调用,如果通过同样的系统调用,就也可以将该空间映射到另一个进程的共享区中,也可以使用虚拟地址访问该内存,所以就让不同的进程看到了同一个内存资源。
共享内存数据结构
一个时刻,在系统中可能存在很多个进程在进行通信,就可能存在很多个共享内存,OS就会将这些共享内存进行管理,先描述再组织。下面是描述共享内存的数据结构。
struct shmid_ds {struct ipc_perm shm_perm; /* operation perms */int shm_segsz; /* size of segment (bytes) */__kernel_time_t shm_atime; /* last attach time */__kernel_time_t shm_dtime; /* last detach time */__kernel_time_t shm_ctime; /* last change time */__kernel_ipc_pid_t shm_cpid; /* pid of creator */__kernel_ipc_pid_t shm_lpid; /* pid of last operator */unsigned short shm_nattch; /* no. of current attaches */unsigned short shm_unused; /* compatibility */void *shm_unused2; /* ditto - used by DIPC */void *shm_unused3; /* unused */
};struct ipc_perm {key_t __key; /* Key supplied to shmget(2) */uid_t uid; /* Effective UID of owner */gid_t gid; /* Effective GID of owner */uid_t cuid; /* Effective UID of creator */gid_t cgid; /* Effective GID of creator */unsigned short mode; /* Permissions + SHM_DEST andSHM_LOCKED flags */unsigned short __seq; /* Sequence number */
};
共享内存存在很多个,那么怎么保证两个进程看到的是同一个共享内存呢?
key_t:共享内存的唯一值。- 我们只需要两个进程,在使用 ftok 形成key时,使用同样的参数即可。
共享内存,如果进程结束,没有主动释放,则会一直存在,除非重启系统。共享内存的生命周期随内核。
查看和删除共享内存资源的命令
ipcs -m:查看系统中指定用户创建的共享内存。
ipcrm -m shmid:删除指定shmid的共享内存。
🌻共享内存函数
shmget函数
功能:用来创建共享内存。
int shmget(key_t key, size_t size, int shmflg);
参数:
- key:这个共享内存段名字 :共享内存在内核中唯一性的标识。
- size:共享内存大小。在内核中,共享内存的大小是以
4KB为基本单位的。如果size设置为4097,则实际开辟的是8KB。 - shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的。
- 返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1。
常用shmflg选项:
- IPC_CREAT:如果创建的共享内存不存在,就创建,如果存在,直接获取它。
- IPC_CREAT:单独使用没有意义。
- IPC_CREAT | IPC_CREAT:如果创建的共享内存不存在,就创建,如果存在,就会出错返回。
- 可以
指定该共享内存的权限,直接 或上权限即可(如 |0666)。
ftok函数
功能:生成一个System V IPC key
key_t ftok(const char *pathname, int proj_id);
用法:传入路径+文件名(随便一个字符串也行)和 一个整型,生成一个System V IPC key.
返回值:成功返回生成的key,失败返回-1,错误码被设置。
shmat函数
功能:将共享内存段连接到进程地址空间(建立映射)
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:
- shmid:共享内存标识。
- shmaddr:指定连接的地址(虚拟地址)。
- shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY。
- 返回值:成功返回一个指针,指向共享内存第一个字节;失败返回-1。
说明:
shmaddr 为 NULL,核心自动选择一个地址。
shmaddr 不为 NULL且smflg 无 SHM_RND标记,则以shmaddr为连接地址。
shmaddr 不为 NULL且shmflg 设置了 SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr -
(shmaddr % SHMLBA)。
shmflg = SHM_RDONLY,表示连接操作用来只读共享内存。
shmdt函数
功能:将共享内存段与当前进程脱离(去关联)。
int shmdt(const void *shmaddr);
参数:
- shmaddr:由shmat所返回的指针。
- 返回值:成功返回0;失败返回-1。
注意:将共享内存段与当前进程脱离不等于删除共享内存段。
shmctl函数
功能:用于控制共享内存。
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:
- shmid:由
shmget返回的共享内存标识码。 - cmd:将要采取的动作(有三个可取值)。
- buf:指向一个保存着共享内存的模式状态和访问权限的数据结构(如果不关心,直接设置为nullptr)。
- 返回值:成功返回0;失败返回-1。
🍃共享内存的优缺点
-
共享内存优点:是进程间通信中速度最快的。(管道通信中,进程需要通过系统调用往管道里面读写数据,两个进程通信一次,至少两次系统调用;而共享内存,进程可以通过共享内存的起始虚拟地址,直接访问到内存,一个进程向共享内存中写了数据,另一个进程就可以直接看到,只需要一次系统调用即可)。 -
共享内存缺点:没有提供进程间任何的协同机制,如果需要,可以自己实现。有些场景不适用,比如我们一个进程发送一个数据,如果没有协同机制,那么可能该进程数据才写一半,另一个进程就读取了,会造成数据不一致。- 可以通过管道来实现同步。示例代码中有体现。
示例代码演示:
Comm.hpp
#pragma once
#include <iostream>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <string>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>using namespace std;const char *pathname = "/home/whb";
const int proj_id = 0x66;// 在内核中,共享内存的大小是以4KB为基本单位的. 你只能用你申请的大小。建议申请大小是n*4KB
const int defaultsize = 4096; // 单位是字节std::string ToHex(key_t k){char buffer[1024];snprintf(buffer, sizeof(buffer), "0x%x", k);return buffer;
}key_t GetShmKeyOrDie(){key_t k = ftok(pathname, proj_id);if (k < 0) {std::cerr << "ftok error, errno : " << errno << ", error string: " << strerror(errno) << std::endl;exit(1);}return k;
}int CreateShmOrDie(key_t key, int size, int flag){int shmid = shmget(key, size, flag);if (shmid < 0){std::cerr << "shmget error, errno : " << errno << ", error string: " << strerror(errno) << std::endl;exit(2);}return shmid;
}int CreateShm(key_t key, int size){// IPC_CREAT: 不存在就创建,存在就获取// IPC_EXCL: 没有意义// IPC_CREAT | IPC_EXCL: 不存在就创建,存在就出错返回return CreateShmOrDie(key, size, IPC_CREAT | IPC_EXCL | 0666);
}int GetShm(key_t key, int size){return CreateShmOrDie(key, size, IPC_CREAT);
}void DeleteShm(int shmid){int n = shmctl(shmid, IPC_RMID, nullptr);if (n < 0){std::cerr << "shmctl error" << std::endl;}else{std::cout << "shmctl delete shm success, shmid: " << shmid << std::endl;}
}void ShmDebug(int shmid){struct shmid_ds shmds;int n = shmctl(shmid, IPC_STAT, &shmds);if (n < 0){std::cerr << "shmctl error" << std::endl;return;}std::cout << "shmds.shm_segsz: " << shmds.shm_segsz << std::endl;std::cout << "shmds.shm_nattch:" << shmds.shm_nattch << std::endl;std::cout << "shmds.shm_ctime:" << shmds.shm_ctime << std::endl;std::cout << "shmds.shm_perm.__key:" << ToHex(shmds.shm_perm.__key) << std::endl;
}void *ShmAttach(int shmid){void *addr = shmat(shmid, nullptr, 0);if ((long long int)addr == -1){std::cerr << "shmat error" << std::endl;return nullptr;}return addr;
}void ShmDetach(void *addr){int n = shmdt(addr);if (n < 0){std::cerr << "shmdt error" << std::endl;}
}
Fofi.hpp
#ifndef __COMM_HPP__
#define __COMM_HPP__#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <cassert>using namespace std;#define Mode 0666
#define Path "./fifo"class Fifo
{
public:Fifo(const string &path = Path) : _path(path){umask(0);int n = mkfifo(_path.c_str(), Mode);if (n == 0){cout << "mkfifo success" << endl;}else{cerr << "mkfifo failed, errno: " << errno << ", errstring: " << strerror(errno) << endl;}}~Fifo(){int n = unlink(_path.c_str());if (n == 0){cout << "remove fifo file " << _path << " success" << endl;}else{cerr << "remove failed, errno: " << errno << ", errstring: " << strerror(errno) << endl;}}
private:string _path; // 文件路径+文件名
};class Sync{
public:Sync() : rfd(-1), wfd(-1){}void OpenReadOrDie(){rfd = open(Path, O_RDONLY);if (rfd < 0)exit(1);}void OpenWriteOrDie(){wfd = open(Path, O_WRONLY);if (wfd < 0)exit(1);}bool Wait(){bool ret = true;uint32_t c = 0;ssize_t n = read(rfd, &c, sizeof(uint32_t));if (n == sizeof(uint32_t)){std::cout << "server wakeup, begin read shm..." << std::endl;}else if (n == 0){ret = false;}else{return false;}return ret;}void Wakeup(){uint32_t c = 0;ssize_t n = write(wfd, &c, sizeof(c));assert(n == sizeof(uint32_t));std::cout << "wakeup server..." << std::endl;}~Sync() {}
private:int rfd;int wfd;
};#endif
ShmClient.cc
#include "Comm.hpp"
#include "Fifo.hpp"
#include <unistd.h>
int main()
{key_t key = GetShmKeyOrDie();std::cout << "key: " << ToHex(key) << std::endl;// sleep(2);int shmid = GetShm(key, defaultsize);std::cout << "shmid: " << shmid << std::endl;// sleep(2);char *addr = (char *)ShmAttach(shmid);std::cout << "Attach shm success, addr: " << ToHex((uint64_t)addr) << std::endl;// sleep(5);memset(addr, 0, defaultsize);Sync syn;syn.OpenWriteOrDie();// 可以进行通信了for (char c = 'A'; c <= 'Z'; c++) // pipe, fifo, ->read/write->系统调用, shm -> 没有使用系统调用!!{addr[c - 'A'] = c;sleep(1);syn.Wakeup();}ShmDetach(addr);std::cout << "Detach shm success, addr: " << ToHex((uint64_t)addr) << std::endl;sleep(5);return 0;
}
Shm.Server.cc
#include "Comm.hpp"
#include "Fifo.hpp"
#include <unistd.h>int main(){// 1. 获取keykey_t key = GetShmKeyOrDie();std::cout << "key: " << ToHex(key) << std::endl;// sleep(2);// 2. 创建共享内存int shmid = CreateShm(key, defaultsize);std::cout << "shmid: " << shmid << std::endl;// sleep(2);// ShmDebug(shmid);// 4. 将共享内存和进程进行挂接(关联)char *addr = (char *)ShmAttach(shmid);std::cout << "Attach shm success, addr: " << ToHex((uint64_t)addr) << std::endl;// 0. 先引入管道Fifo fifo;Sync syn;syn.OpenReadOrDie();// 可以进行通信了for(;;){if(!syn.Wait()) break;cout << "shm content: " << addr << std::endl;}ShmDetach(addr);std::cout << "Detach shm success, addr: " << ToHex((uint64_t)addr) << std::endl;// 3. 删除共享内存DeleteShm(shmid);return 0;
}
🦔system V消息队列(了解)
消息队列提供了一个从一个进程向另外一个进程发送一个有类型的数据块的方法。- 每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值。
- 特性方面
- IPC资源必须删除,否则不会自动清除,除非重启,所以
system V IPC资源的生命周期随内核。
- IPC资源必须删除,否则不会自动清除,除非重启,所以
进程A与进程B,其中,进程A可以向内核中发送和读取数据块,进程B也可以发送和读取,发送的数据块放到一个队列中,当进程A在发送的时候,另一个进程也可以发送,数据块中有标记位标记是谁发送的。
消息队列的数据结构:
struct msqid_ds {struct ipc_perm msg_perm; /* Ownership and permissions */time_t msg_stime; /* Time of last msgsnd(2) */time_t msg_rtime; /* Time of last msgrcv(2) */time_t msg_ctime; /* Time of creation or lastmodification by msgctl() */unsigned long msg_cbytes; /* # of bytes in queue */msgqnum_t msg_qnum; /* # number of messages in queue */msglen_t msg_qbytes; /* Maximum # of bytes in queue */pid_t msg_lspid; /* PID of last msgsnd(2) */pid_t msg_lrpid; /* PID of last msgrcv(2) */
};
struct ipc_perm {key_t __key; /* Key supplied to msgget(2) */uid_t uid; /* Effective UID of owner */gid_t gid; /* Effective GID of owner */uid_t cuid; /* Effective UID of creator */gid_t cgid; /* Effective GID of creator */unsigned short mode; /* Permissions */unsigned short __seq; /* Sequence number */
};
消息队列的相关操作接口(相关接口的参数与共享内存类似)
-
创建消息队列
int msgget(key_t key,int msgflg); -
删除消息队列
int msgctl(int msqid,int cmd,struct msqid_ds *buf); -
向队列里发送数据
int msgsnd(int msqid,const void* msgp,size_t msgsz,int msgflq); -
读取数据
ssize_t msgrcv(int msqid,void* msgp,size_t msgsz,long msgtyp,int msgflg); -
查找消息队列的指令
ipcs -q -
删除消息队列的指令
ipcrm -q msqid
🦅system V信号量 sem
信号量主要用于同步和互斥的,下面先来看看什么是同步和互斥。
- 进程互斥 ·
- 由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为
进程的互斥。 - 系统中某些资源一次只允许一个进程使用,称这样的资源为
临界资源或互斥资源。 - 访问临界资源的代码段叫
临界区。
- 由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为
- 特性方面
- IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期
随内核。
- IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期
📚信号量理论
信号量(信号灯)
- 对于一份内存资源(临界资源),如果只允许整体被使用的话,每一次只允许一个进程访问,这种工作做方式效率低。
- 其中,
信号量就提供了一种机制,将一份内存资源拆分成很多小资源,允许多个进程同时访问,前提:每一个进程访问的是不同的被切分的小资源,就能做到真正的并发访问。
在这种情况下,只需要1.限制进来的进程数。2.合理分配资源 即可。
信号量的本质是一个计数器,是描述临界资源数量的计数器。(如下图,假设计数器 int count = 9;)
在访问资源的时候,会经历一下几个步骤:
- 申请信号量,if count>0,则申请成功,并且会将count–,也叫做
p操作; - 访问临界资源。
- 释放信号量,count++,叫做
v 操作;
在多进程场景,int不能实现信号量得效果,原因如下:
- count
++/--不是原子的,所以在++/–过程中,可能有其他进程也会申请信号量,会造成访问冲突问题。(比如现在count=1,然后一个进程来申请,然后再count–过程中,这时count还是1,又有另一个进程来申请信号量,又会count–,count–完成后,就变为成-1了)。 无法在进程间共享,变量在进程中做修改时,会发生写时拷贝。所以须让不同的进程先看到同一份资源—计数器资源!所以信号量属于进程间通信。
所有进程,访问临界资源,都必须先申请信号量–所有的进程都得先看到一个信号量–所以信号量本身就是共享资源!所以信号量的申请(++)和释放(–)都必须是原子的。pv操作必须是原子的。
如果信号量的初始值是1,就是互斥,也就是二元信号量,也是后面文章会说到的 锁。
信号量的数据结构:
struct semid_ds {struct ipc_perm sem_perm; /* Ownership and permissions */time_t sem_otime; /* Last semop time */time_t sem_ctime; /* Creation time/time of lastmodification via semctl() */unsigned long sem_nsems; /* No. of semaphores in set */
};
struct ipc_perm {key_t __key; /* Key supplied to semget(2) */uid_t uid; /* Effective UID of owner */gid_t gid; /* Effective GID of owner */uid_t cuid; /* Effective UID of creator */gid_t cgid; /* Effective GID of creator */unsigned short mode; /* Permissions */unsigned short __seq; /* Sequence number */
};
信号量的相关操作接口
-
申请信号量
int semget(key_t key, int nsems, int semflg); -
控制信号量
int semid, int semnum, int cmd, ...); -
pv操作
int semop(int semid, struct sembuf *sops, size_t nsops);
指令查找以及删除信号量:
- 查找:
ipcs - s - 删除:
ipcrm -s semid
共享内存 信号量 消息队列 是OS系统特意设计的 system V进程间通信的。
共享内存 信号量 消息队列可以看成同一种资源,IPC资源,OS注定要对IPC资源进程管理–先描述再组织。
我们可以发现,在内核中,无论是共享内存 ,信号量 还是消息队列,描述他们的结构体里的第一个成员都是 kern ipc_perm类型的。在内核中,有一个数据结构ipc_id_ary,里面第一个成员是size,其余都是类似于指针数组,类型为 kern ipc_perm*,当系统创建了一个IPC 资源时,会将该资源的数据结构的第一个成员地址存放在该kern ipc_perm*数组中,这样,系统中的每一个IPC
资源都组织起来了,方便管理。类似于C++中的多态。
相关文章:
System V IPC奥秘:解锁共享内存、消息队列与信号量的高效通信之路
🍑个人主页:Jupiter. 🚀 所属专栏:Linux从入门到进阶 欢迎大家点赞收藏评论😊 目录 🍑system V共享内存 🍒共享内存的原理共享内存数据结构查看和删除共享内存资源的命令 🌻共享内存…...
怎么将pdf转为ppt文件?pdf转ppt的8个方法
在诸多职场与学术交流的场合中,我们时常面临将详尽的PDF文件转化为生动且易于编辑的PPT演示文稿的需求。这一转换不仅是为了满足演示时的灵活性,更是为了提升信息传递的效率与观众的理解度。从简单的在线工具到功能全面的专业软件,我们拥有多…...
【Datawhale AI夏令营第四期】 魔搭-大模型应用开发方向笔记 Task03 大咖项目分享 人话八股文Bakwaan_Buddy项目开发尝试
【Datawhale AI夏令营第四期】 魔搭-大模型应用开发方向笔记 Task03 人话八股文Bakwaan_Buddy项目开发尝试 Idea: 我们草台班子目前的想法是解决大家计算机学院毕业面临的BUG——不爱背、背不下来八股文,觉得枯燥、烦、工作了用不着,反正就是知识他不进…...
如何在wordpress当中使用插件WP Coder(将html、css、javascript应用到wordpress上)
了解认识阶段 安装并运行好WP Coder之后如下图: 设置全局PHP 禁用gutenberg 输入代码 add_filter(gutenberg_can_edit_post, __return_false, 10); add_filter(use_block_editor_for_post, __return_false, 10); 记得点击save并勾选enable PHP code 禁用之后打…...
ActiveMQ、RabbitMQ、Kafka、RocketMQ在消息回溯、消息堆积+持久化、消息追踪、消息过滤的区别
ActiveMQ、RabbitMQ、Kafka、RocketMQ在消息回溯、消息堆积持久化、消息追踪、消息过滤等方面各有其独特的特点和优势。以下是这四个方面的详细比较: 1. 消息回溯 ActiveMQ:支持消息回溯功能。ActiveMQ可以将消息持久化到磁盘上,因此当需要…...
使用ITextRenderer导出PDF后无法打开问题,提示‘无法打开此文件‘
依赖如下 <!-- https://mvnrepository.com/artifact/org.xhtmlrenderer/flying-saucer-pdf --> <dependency><groupId>org.xhtmlrenderer</groupId><artifactId>flying-saucer-pdf</artifactId><version>9.1.22</version> &l…...
STL必须掌握的几大常见算法
1. std::sort 功能:对容器中的元素进行排序。 用法: #include <algorithm> #include <vector>std::vector<int> vec = {3, 1, 4, 1, 5, 9}; std::sort(vec.begin(), vec.end());你知道sort内部用的什么排序吗 2. std::reverse 功能:将容器中的元素顺…...
HAproxy 七层负载均衡调度器详解及配置
HAproxy 七层负载均衡 负载均衡技术 负载均衡(Load Balance):一种服务,或基于硬件设备实现的高可用的反向代理技术,是指将特定的业务流量分摊给一个或多个后端的特定服务器或设备,实现高并发处理业务流量…...
Python学习笔记--私有属性、构造方法、析构方法、常用内置方法
目录 私有属性 构造方法 析构方法 常用内置方法 私有属性 1. 私有属性是指在类内可以直接访问、而在类外无法直接访问的属性 2. Python中规定,在定义类时,如果一个类属性名是以__(两个下划线)开头,则该类属性为私…...
4章8节:用R做数据重塑,行列命名和数据类型转换
在R语言中,行列命名和数据类型转换是数据处理中的两个基础性操作。它们不仅对数据的可读性和组织性至关重要,而且在执行数据分析、模型构建和结果解释时也扮演着重要的角色。 一、行和列命名 在数据科学和统计分析中,命名是组织和管理数据的一个重要部分。尤其是在处理复杂…...
浏览器发出请求到响应的过程
客户端发起请求:用户输入URL并回车,浏览器解析URL,生成HTTP请求。 DNS解析:浏览器查看本地hosts文件是否有域名的IP地址映射,如果没有则向DNS服务器发起解析请求,获取域名对应的IP地址。 建立TCP连接&…...
eNSP 华为划分VLAN
SW1: <Huawei>system-view [Huawei]sysname SW1 [SW1]VLAN batch 10 20 //批量划分vlan [SW1]interface GigabitEthernet0/0/1 [SW1-GigabitEthernet0/0/1]port link-type access //设置为access口,access口允许单个vlan通过 [SW1-GigabitEth…...
公用事业公司签署大规模电力供应协议
随着人工智能技术的迅猛发展,美国公用事业公司与数据中心运营商之间的电力供应协议数量显著增加,为未来几季度的销售和利润增长奠定了基础。根据高盛今年5月发布的一份报告,到2030年,数据中心的发电量预计将占美国总发电量的8%&am…...
C语言 | Leetcode C语言题解之第341题扁平化嵌套列表迭代器
题目: 题解: struct NestedIterator {int *vals;int size;int cur; };void dfs(struct NestedIterator *iter, struct NestedInteger **nestedList, int nestedListSize) {for (int i 0; i < nestedListSize; i) {if (NestedIntegerIsInteger(neste…...
冷知识:编程第一人是位伟大的女性
冷门智慧:阿达编程先驱的传奇人生揭秘在线播放免费听 - 喜马拉雅手机版欢迎收听由主播壹道徽为您带来的“冷门智慧:阿达编程先驱的传奇人生揭秘”精彩有声内容,该音频时长5分18秒,已被收听1062次,用户嘎嘎呗嘎嘎评价说…...
Python爬虫使用实例
IDE:大部分是在PyCharm上面写的 解释器装的多 → 环境错乱 → error:没有配置,no model 爬虫可以做什么? 下载数据【文本/二进制数据(视频、音频、图片)】、自动化脚本【自动抢票、答题、采数据、评论、点…...
主成分分析(PCA)
1 主成分分析简介 主成分分析(Principal Component Analysis,PCA), 将多个变量通过线性变换以选出较少个数重要变量的一种多元统计分析方法。主成分分析是由卡尔皮尔逊(Karl Pearson)于1901年发明的。通过维度约减的方式将高维度…...
python实现生命游戏
“生命游戏”(Conway’s Game of Life)是由数学家约翰康威提出的一种零玩家游戏。它是一个细胞自动机,由一组简单的规则决定每个细胞的状态。以下是用Python实现“生命游戏”的代码示例: Python代码实现 import numpy as np imp…...
基于vue框架的CIA报价平台的设计与实现1xv02(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。
系统程序文件列表 项目功能:用户,供应商,产品分类,产品信息,在线咨询,资质申请 开题报告内容 基于Vue框架的CIA报价平台的设计与实现 开题报告 一、选题背景 随着市场竞争的日益激烈,企业对于成本控制与效率提升的需求愈发迫切。在采购与供应链管理…...
【Kubernetes】k8s集群Pod控制器
目录 一.Pod控制器作用 二.Pod控制器类型 1.Deployment(简称deploy) ReplicaSet(简称rs) 2.StatefulSet(简称sts) 创建SatefulSet控制器 3.DaemonSet(简称ds) 4.Job 5.Cron…...
springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...
【Oracle APEX开发小技巧12】
有如下需求: 有一个问题反馈页面,要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据,方便管理员及时处理反馈。 我的方法:直接将逻辑写在SQL中,这样可以直接在页面展示 完整代码: SELECTSF.FE…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...
佰力博科技与您探讨热释电测量的几种方法
热释电的测量主要涉及热释电系数的测定,这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中,积分电荷法最为常用,其原理是通过测量在电容器上积累的热释电电荷,从而确定热释电系数…...
C++.OpenGL (20/64)混合(Blending)
混合(Blending) 透明效果核心原理 #mermaid-svg-SWG0UzVfJms7Sm3e {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-icon{fill:#552222;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-text{fill…...
(一)单例模式
一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...
MySQL 索引底层结构揭秘:B-Tree 与 B+Tree 的区别与应用
文章目录 一、背景知识:什么是 B-Tree 和 BTree? B-Tree(平衡多路查找树) BTree(B-Tree 的变种) 二、结构对比:一张图看懂 三、为什么 MySQL InnoDB 选择 BTree? 1. 范围查询更快 2…...
C++实现分布式网络通信框架RPC(2)——rpc发布端
有了上篇文章的项目的基本知识的了解,现在我们就开始构建项目。 目录 一、构建工程目录 二、本地服务发布成RPC服务 2.1理解RPC发布 2.2实现 三、Mprpc框架的基础类设计 3.1框架的初始化类 MprpcApplication 代码实现 3.2读取配置文件类 MprpcConfig 代码实现…...
