Linux之进程间通信(二)

news2024/11/15 17:58:43

system V

system V共享内存内核中专门设计的通信的方式,  粗粒度划分操作系统分为进程管理, 内存管理, 文件系统, 驱动管理.., 粒度更细地分还有 进程间通信模块.

对于操作系统, 通信的场景有很多, 有以传送数据, 快速传送数据, 传送特定数据块, 进程间协同与控制以目的, 它们在接口实现上都不相同, 所以把操作系统中通信的方式聚集在一块, 接口统一之后形成了限于本主机通信的一种模式叫systemV

管道不属于system V, 它们是复用操作系统源代码, 属于比较原始的通信方式.


进程间通信的前提:必须让不同的进程看到同一份资源(必须由OS提供)

共享内存

原理

共享内存区是最快的IPC形式. 一旦这样的内存 映射到共享它的进程的地址空间, 这些进程间数据递不再涉及到内核, 换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据.

共享内存是专门设计用于IPC的, 它与进程地址空间共享区有关.

共享内存作为一种通信方式, 所有关联了这块内存的进程都可以使用它, 这块内存不再是只属于一个进程的.

1. 操作系统中一定会同时存在很多的共享内存, 所以共享内存也要被操作系统管理,
要认识到开辟一块空间的内存占用应当是物理内存空间共享内存的相关属性. 操作系统管理共享内存属性就是对其数据结构进行增删查改, 从而对它对应的物理内存空间进行实时监控管理.

共享内存结构: 

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 and
                                           SHM_LOCKED flags */
               unsigned short __seq;    /* Sequence number */
           };

2. 因为操作系统内一定会同时存在许多共享内存, 那这么多的共享内存中如何保证 A进程创建的共享内存 需要和A通信的进程, 看到的是同一份共享内存呢?

凡是被创建共享内存, 相关属性结构体中必须有一个能体现出该共享内存的唯一性的数据,这个数据就是key. key不光是为了保证共享内存的唯一性, 也是为了通过 key 能够让其他进程识别该共享内存, 从而不同的进程才能选择特定的共享内存进行通信.

key被封装在上面的struct ipc_perm结构体内.

关于唯一性标识, 除了key还有一个叫shmid,  在应用这个共享内存的时候, 我们使用shmid来进行操作共享内存,类似文件操作的fd, 而 key 不要在应用层使用, 只用来在内核中标识shm的唯一性! 类似inode.

关于 key 和 shmid 需要介绍一下相关系统接口: 


系统接口

ftok

功能:用 路径名 和 项目标识符 转换为唯一标识符key返回

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

参数:

  • pathname: 文件路径名
  • proj_id: 项目标识符
  • 返回值: 标识某一个共享内存的key值

key的作用: 这里的key用于形成共享内存的唯一标识shmid, 而这个key的生成规则是通过路径名和项目标识符生成的, 所以对于同一个共享内存的使用者就可以通过规定这两个参数都能得到同样的key, 从而通过shmget创建/获取同样的共享内存.

shmget 

功能: 用来创建共享内存
原型: int shmget(key_t key, size_t size, int shmflg);
参数:

  • key: 这个共享内存段名字
  • size: 共享内存大小, 建议为4096的倍数
  • shmflg: 由九个权限标志构成, 它们的用法和创建文件时使用的mode模式标志是一样的
  • 返回值:成功返回一个非负整数, 即该共享内存段的标识码;失败返回-1 

 创建共享内存需要用到这两个标志:

首先要明确, shmget接口可以实现创建和使用共享内存,

1. 单独传入IPC_CREAT, 共享内存 存在就返回, 不存在就创建并返回

2. 传入IPC_CREAT|IPC_EXCL, 共享内存 存在就报错, 不存在就创建并返回, 注意IPC_EXCL单独传入没有意义, 和IPC_CREAT一起传入才有意义, 这两个标志位保证了创建的共享内存是全新的.

3. 传入0, 共享内存存在就返回, 不存在就报错.

 shmat(at = attach)

功能: 将共享内存段连接到进程地址空间
原型: void * shmat(int shmid, const void *shmaddr, int shmflg);
参数:

  • shmid: 共享内存标识
  • shmaddr: 指定连接的地址
  • shmflg: 它的两个可能取值是 SHM_RND 和 SHM_RDONLY
  • 返回值: 成功返回一个指针, 指向共享内存首地址; 失败返回-1

这个返回值和 malloc 是类似的, 都可以强转为指定类型的地址, 但是malloc是在堆区开辟一块空间, 并不是共享内存.

shmdt (dt = detach)

功能: 将共享内存段与当前进程脱离
原型: int shmdt(const void *shmaddr);
参数:

  • shmaddr: 由shmat所返回的指针
  • 返回值:成功返回0; 失败返回-1

注意: 将共享内存段与当前进程脱离不等于删除共享内存段

shmctl

功能: 用于控制共享内存
原型: int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:

  • shmid: 由shmget返回的共享内存标识码.
  • cmd: 将要采取的动作 (有三个可取值).
  • buf: 指向一个保存着共享内存的模式状态和访问权限的数据结构.
  • 返回值: 成功返回0;失败返回-1.

共享内存的生命周期:

共享内存的生命周期不会随着内存的释放而结束, 也就是说程序结束后如果没有用shmctl删除共享内存, 共享内存依然存在. 只要系统不重启或显式地删除该共享内存, 它就会一直存在, 一个进程退出, 其他进程仍然可以通过 shmat连接到共享内存并使用它, 直到调用 shmctl 删除共享内存为止.

代码

实现一个server端和client端, server作为读端每隔两秒打印共享内存中的内容, client每隔一秒进行写入: 

 comm.hpp

#include <iostream>
#include <cstring>
#include <cerrno>
const char* pathName = "/home/zzy/linux_system_programing/inter-process_communication";
const int project_id = 0x11223344;
// 共享内存的大小,强烈建议设置成为n*4096
const int size = 4096;

//创建一个key
key_t GetKey()
{
    key_t key = ftok(pathName, project_id);
    if(key < 0)
    {
        std::cerr << "errno:" << errno <<  ", errno string:" << strerror(errno)<<std::endl;
        exit(1);
    }
    return key;
}

//将key转换为0x开头字符串
std::string ToHex(key_t key)
{
    char buffer[1024];
    snprintf(buffer, sizeof(buffer), "0x%x", key);
    return buffer;
}

//子函数
int CreateShmHelper(key_t key, int shmflg)
{
    int shmid = shmget(key, size, shmflg);
    if(shmid < 0)
    {
        std::cerr << "errno:" << errno <<  ", errno string:" << strerror(errno)<<std::endl;
        exit(2);
    }
    return shmid;
}

//创建共享内存
int CreateShm(key_t key)
{
    return CreateShmHelper(key, IPC_CREAT|IPC_EXCL|0644);
}

//获取共享内存
int GetShm(key_t key)
{
    return CreateShmHelper(key, IPC_CREAT);//传0也可以
}

server.cc 

#include <iostream>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include "comm.hpp"
using namespace std;

int main()
{
    key_t key = GetKey();
    cout << "key: " << ToHex(key) << endl;

    // key vs shmid
    // shmid: 应用这个共享内存的时候,我们使用shmid来进行操作共享内存,类似文件操作的FILE*
    // key: 不要在应用层使用,只用来在内核中标识shm的唯一性! 类似文件操作的fd
    int shmid = CreateShm(key);
    cout << "shmid: " << shmid << endl;

    char* s = (char*)shmat(shmid, nullptr, 0);
    cout << "shm attach done: " << shmid << endl;

    while(true)
    {
        cout << s << endl;
        sleep(2);
    }

    //取消共享内存与进程地址空间的映射
    shmdt(s);
    cout << "shm dettach done: " << shmid << endl;
    sleep(3);

    //删除共享内存
    shmctl(shmid, IPC_RMID, nullptr);
    cout << "shm remove done " << shmid << endl;
    return 0;
}

client.cc 

#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include "comm.hpp"
using namespace std;

int main()
{
    key_t key = GetKey();
    cout << "key: " << ToHex(key) << endl;
    
    int shmid = GetShm(key);
    cout << "shmid: " << shmid << endl;

    char* s = (char*)shmat(shmid, nullptr, 0);
    cout << "shm attach done: " << shmid << endl;

    for(char c = 'a'; c<='z'; c++)
    {
        s[c-'a'] = c;
        cout << "write " << c << " done" <<endl;
        sleep(1);
    }

    shmdt(s);
    cout << "shm dettach done: " << shmid << endl;

    return 0;
}

由结果可以看到, 当server端启动后, client也启动然后与shm连接, 并每隔1秒向shm中发送数据, 而server每隔2秒读取shm中的内容 :

共享内存的同步方式: 不提供同步机制, 共享内存是直接裸露给所有的使用者使用的, 一定要注意共享内存的使用安全问题. 

借助管道的同步机制, 可以让共享内存实现同步:

comm.hpp添加新建管道文件的函数: 

//创建管道
bool MakeFifo()
{
    int fd = mkfifo(filename.c_str(), 0666);
    if(fd < 0)
    {
        std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;
        return false;
    }
    std::cout << "mkfifo success... read" << std::endl;
    return true;
}

 server.cc借助类修改了一下初始化和清理资源的方式, 然后在while循环中读取管道传来的内容以实现同步:

#include <iostream>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include "comm.hpp"
using namespace std;

class Init
{
public:
    Init()
    {
        bool r = MakeFifo();
        if (!r)
            return;

        key_t key = GetKey();
        cout << "key: " << ToHex(key) << endl;

        // key vs shmid
        // shmid: 应用这个共享内存的时候,我们使用shmid来进行操作共享内存,类似文件操作的FILE*
        // key: 不要在应用层使用,只用来在内核中标识shm的唯一性! 类似文件操作的fd
        shmid = CreateShm(key);
        cout << "shmid: " << shmid << endl;

        s = (char *)shmat(shmid, nullptr, 0);
        cout << "shm attach done: " << shmid << endl;

        fd = open(filename.c_str(), O_RDONLY);
    }

    ~Init()
    {
        // 取消共享内存与进程地址空间的映射
        shmdt(s);
        cout << "shm dettach done: " << shmid << endl;
        // 删除共享内存
        shmctl(shmid, IPC_RMID, nullptr);
        cout << "shm remove done " << shmid << endl;

        close(fd);
        unlink(filename.c_str());
    }

public:
    char *s;
    int shmid;
    int fd;
};

int main()
{
    Init init;

    while (true)
    {
        int code = 0;
        ssize_t n = read(init.fd, &code, sizeof(code));
        if(n > 0)
        {
            cout << init.s << endl;
            //sleep(2);
        }
        else if(n == 0)
        {
            break;
        }
    }

    return 0;
}

client.cc 向共享内存写入一个字符就向管道传入内容通知server可以读取数据了(同步): 

#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include "comm.hpp"
using namespace std;

int main()
{
    key_t key = GetKey();
    cout << "key: " << ToHex(key) << endl;
    
    int shmid = GetShm(key);
    cout << "shmid: " << shmid << endl;

    char* s = (char*)shmat(shmid, nullptr, 0);
    cout << "shm attach done: " << shmid << endl;
    int fd = open(filename.c_str(), O_WRONLY);

    for(char c = 'a'; c<='z'; c++)
    {
        s[c-'a'] = c;
        cout << "write " << c << " done" <<endl;
        //通知对方
        int code = 1;
        ssize_t n = write(fd, &code, sizeof(code));
        sleep(1);
    }

    shmdt(s);
    cout << "shm dettach done: " << shmid << endl;
    close(fd);

    return 0;
}

这次可以发现 client 向 shm 写入一个数据, 就发送一次接收的指令, server就输出一次共享内存的内容, 完成了同步. 

关于共享内存的内容如何清理:

shm只涉及进程向 shm 中写入和读取内容, 而 shm 中的内容并不会因为内容的读取就被移除, 所以shm的内容需要用户自己清理, 如何清理? 可以规定shm的前8/16个字节存放两个写入和读取指针, 通过两个指针对shm的内容进行维护.

 关于shmctl接口查看共享内存结构体:

上面使用过IPC_RMID删除共享内存, 现在用 IPC_STAT 查看shmid_ds中的内容:

#include <iostream>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include "comm.hpp"
using namespace std;

class Init
{
public:
    Init()
    {
        key_t key = GetKey();
        cout << "key: " << ToHex(key) << endl;

        // key vs shmid
        // shmid: 应用这个共享内存的时候,我们使用shmid来进行操作共享内存,类似文件操作的FILE*
        // key: 不要在应用层使用,只用来在内核中标识shm的唯一性! 类似文件操作的fd
        shmid = CreateShm(key);
        cout << "shmid: " << shmid << endl;

        s = (char *)shmat(shmid, nullptr, 0);
        cout << "shm attach done: " << shmid << endl;     
    }

    ~Init()
    {
        // 取消共享内存与进程地址空间的映射
        shmdt(s);
        cout << "shm dettach done: " << shmid << endl;
        // 删除共享内存
        shmctl(shmid, IPC_RMID, nullptr);
        cout << "shm remove done " << shmid << endl;
    }

public:
    char *s;
    int shmid;
    int fd;
};

int main()
{
    Init init;

    struct shmid_ds ds;
    shmctl(init.shmid, IPC_STAT, &ds);
    std::cout << ToHex(ds.shm_perm.__key) << std::endl;
    std::cout << ds.shm_segsz << std::endl;
    std::cout << ds.shm_atime << std::endl;
    std::cout << ds.shm_cpid << std::endl;
    std::cout << ds.shm_nattch << std::endl;

    return 0;
}

总结:

缺点:

共享内存的同步方式, 不会提供同步机制, 共享内存是直接裸露给所有的使用者使用的, 一定要注意共享内存的使用安全问题. 

优点:

1. 共享内存是所有进程间通信速度最快的

2. 共享内存可以提供较大的空间

第二点进行具体说明:

因为拷贝次数少, 共享内存是所有进程间通信方式中速度最快的. 在同样的代码下, 考虑键盘输入显示器输出(不考虑printf和scanf的缓冲区). 对比数据通过 共享内存 与 管道 通信的拷贝次数.

首先要明确, 凡事涉及到数据的迁移, 都是拷贝.

管道通信会经过四次拷贝: 输入文件(键盘)->用户缓冲区->管道文件缓冲区(内核空间)->用户缓冲区->输出文件(显示器)

共享内存通信只需要两次拷贝:输入文件(键盘)->共享内存->输出文件(显示器)


消息队列

功能:

  • 消息队列提供了一个从一个进程向另外一个进程发送一个数据块的方法, 消息队列和共享内存在使用上有一定差别, 但是它们的共享机制是一样的.
  • 每个数据块都被认为是有一个类型, 接收者进程接收的数据块可以有不同的类型值 

特性: 

同共享内存一样, 消息队列也属于systemV, 而IPC资源必须删除, 否则不会自动清除, 除非重启,所以system V IPC资源的生命周期内核 


消息队列的相关接口和共享内存都是相似的, 因为都属于systemV:

系统接口

msgget 

功能: 用来创建消息队列
原型: int msgget(key_t key, int shmflg);
参数:

  • key: 这个消息队列段名字
  • shmflg: 和共享内存一样
  • 返回值:成功返回一个非负整数, 即该消息队列段的标识码;失败返回-1 
msgctl 

功能: 用于控制消息队列
原型: int msgctl(int msqid, int cmd, struct msqid_ds *buf);
参数:

  • msqid: 由msgget返回的共享内存标识码.
  • cmd: 将要采取的动作 
  • buf: 指向一个保存着消息队列的模式状态和访问权限的数据结构.
  • 返回值: 成功返回0;失败返回-1.
 msgsnd

功能: 用于消息队列发消息

原型: int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

参数:

msqid:msgget的返回值

msgp: 要发送的数据块, 其中包含了数据的类型和内容, 需要用户自己去定义

msgsz: 数据块大小

msgflg: 

msgrcv 

功能: 接收消息队列的消息

原型: ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

参数:

msgflg:

IPC_NOWAIT:如果没有符合条件的消息可用,不阻塞进程,而是立即返回 EAGAIN 错误

MSG_EXCEPT: msgtype大于0的前提下, 接收除msgtpe以外类型的所有消息

msgtyp:

  • 如果 msgtyp 大于 0,则 msgrcv 函数将接收消息队列中类型字段等于 msgtyp 的第一条消息。
  • 如果 msgtyp 等于 0,则 msgrcv 函数将接收消息队列中的第一条消息。
  • 如果 msgtyp 小于 0,则 msgrcv 函数将接收消息队列中类型字段小于或等于 msgtyp 绝对值的第一条消息

更多可以查看手册

void MsgQueue()
{
    //创建消息队列
    key_t key = GetKey();
    std::cout << "key: " << ToHex(key) << std::endl;

    int msgid = msgget(key, IPC_CREAT|IPC_EXCL);
    std::cout << "msgid: " << msgid << std::endl;

    //读取消息队列结构体
    struct msqid_ds ds; 
    msgctl(msgid, IPC_STAT, &ds);
    std::cout << ToHex(ds.msg_perm.__key) << std::endl;
    std::cout << ds.msg_qbytes << std::endl;
    sleep(10);

    //删除消息队列
    msgctl(msgid, IPC_RMID, nullptr);

}

运行结果: 

ipcs -q 

不同于共享内存, 这里内核里的key和我们传入key是不一样的.


信号量

信号量的本质是一个计数器, 用于保护共享资源. 信号量主要用于同步和互斥的,下面先来看看什么是同步和互斥

多个执行流看到的同一份资源(公共资源), 在并发访问时很有可能会发生数据不一致的问题, 所以公共资源就需要被保护起来, 就有了互斥与同步. (匿名管道, 命名管道, 消息队列都由OS提供了保护措施, 而共享内存需要用户自己保证安全, 比如上面代码用管道实现同步)

互斥: 任何一个时刻只允许一个执行流(进程)访问公共资源, 加锁完成

同步: 多个执行流执行的时候, 按照一定的顺序执行.

原子性: 一个操作或者一系列操作要么全部执行成功, 要么全部不执行不存在中间状态或者部分执行的情况.

临界资源: 被保护起来的公共资源

临界区: 访问该临界资源的代码, 维护临界资源, 其实就是在维护临界区

如何理解信号量?

举个例子, 比如看电影

每个电影院的座位都是有限的, 如果我们买了票, 即使不去使用这个座位, 这个座位也已经预定为我们的座位, 电影院和内部的座位是多个人共享的资源--公共资源, 我们买票的本质, 则是对资源的预定机制. 而一场电影的票的数量是有限的, 所以就需要设置一个计数器表示公共资源的个数, 买了一张票, 计数器就减一;退一张票, 计数器就加1, 如果计数器为0, 则买票失败.

信号量:

表示资源数目的计数器, 每一个执行流想要访问公共资源内部的某一份资源, 不应该让执行流直接访问, 而是先申请信号量资源, 申请成功就对信号量计数器做--操作, 申请不成功, 执行流将被挂起阻塞.

本质上, 只要--成功, 就完成了对资源的预定机制.

 假如一份资源的信号量为1, 它被称为二元信号量, 也叫互斥锁, 完成资源的互斥功能.

 所以如果一份共享内存整体只想被一个进程使用,可以使用二元信号量,以信号量的方式实现加锁解锁;如果所有进程都只想使用公共资源的一部分,比如一块16kb的共享内存每个进程只使用局部的一部分比如1kb,所以信号量可以设置为16,每个进程想访问这块共享内存首先要去申请信号量,申请成功才能使用,不成功则阻塞。

关于信号量的细节问题:

关于信号量的描述只是以整数计数器的形式去描述,但是其实并不是。

1. 每个进程访问共享资源都要先申请信号量意味着每个进程都得先看到同一个信号量资源,这只能由OS提供,所以信号量就也属于IPC体系, 不仅仅进程间通信属于IPC体系, 保证通信的安全也属于.

2.  信号量本质也是公共资源, 信号量是为了保护公共资源的, 但是谁去保护信号量呢, 所以对于信号量内部计数器的++或--操作, 必须是原子性的! (具体在多线程部分说明)

所以原子性的申请资源(--)的操作, 称为P; 原子性的释放资源(++)的操作, 称为V

3.  对于进程挂起/阻塞如何理解? 对于单个信号量, 简单地理解其结构为struct sem{int count; task_struct* wait_queue;} , 当信号量申请失败, 把进程设为阻塞状态并加入到对应的阻塞队列即可.

 


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

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

相关文章

一键设置jdk环境脚本

自动化脚本 一、使用方法 创建一个txt文本&#xff0c;放在和jdk存放的同一目录下&#xff0c;复制粘贴进我的代码&#xff0c;利用全局替换&#xff0c;将jdk1.8,改成你自己的jdk包名字&#xff0c;再重新把这个文件保存为.vbs文件。然后运行就行了 MsgBox "Runing s…

线上社交app的搭建,圈子社交系统,小程序+app+H5三端,源码交付,支持二开!

在科技飞速发展的大背景下&#xff0c;年轻人社交不再局限于面对面&#xff0c;线上社交app已深入各大年轻人的手机中。相比于传统交友方式&#xff0c;线上社交app为用户提供了更加新奇的交友体验。同时&#xff0c;它还可以吸引更多的朋友&#xff0c;提高用户的整体交友体验…

第4篇:创建Nios II工程之Hello_World<三>

Q&#xff1a;接着我们再来完成Nios II软件工程设计部分。 A&#xff1a;从Quartus Tools选择Nios II Software Build Tools for Eclipse&#xff0c;打开Nios II SBT软件&#xff0c;Workspace指定到hello_world工程的software文件夹路径&#xff1b;再从File-->New-->…

Java学习13

目录 一.内部类: 1.概念&#xff1a; 2.内部类的分类&#xff1a; &#xff08;1&#xff09;定义在外部类的局部位置上&#xff08;通常在方法体中&#xff09;&#xff1a; 1. 局部内部类&#xff08;有类名&#xff09; 2.匿名内部类&#xff08;无类名-重点&#xff01;…

Linux 基础命令使用创建用户

浏览网站的时候图片&#xff0c;看到一个小练习。创建用户分别位于不同的用户组。 解答下面的题目 2、建立用户使用 useradd&#xff0c;设置密码使用passwd的命令。大概不会使用命令可以借助man来解答。 先建立用户组&#xff1a; groupadd group1 # group1 不存在先建立&…

Redis底层数据结构之ZSkipList

目录 一、概述二、ZSkipList结构三、和平衡树和哈希表的对比 redis底层数据结构已完结&#x1f44f;&#x1f44f;&#x1f44f;&#xff1a; ☑️redis底层数据结构之SDS☑️redis底层数据结构之ziplist☑️redis底层数据结构之quicklist☑️redis底层数据结构之Dict☑️redis…

可替代IBM DOORS的现代化需求管理解决方案Jama Connect,支持数据迁移及重构、实时可追溯性、简化合规流程

作为一家快速发展的全球性公司&#xff0c;dSPACE一直致力于寻找保持领先和优化开发流程的方法。为推进其全球现代化计划&#xff0c;dSPACE开始寻找可以取代传统需求管理平台&#xff08;IBM DOORS&#xff09;的需求管理解决方案。 通过本次案例&#xff0c;您将了解dSPACE为…

【YOLO 系列】基于YOLO V8的火灾烟雾监控检测识别系统【python源码+Pyqt5界面+数据集+训练代码】

摘要&#xff1a; 火灾烟雾的及时检测对于人们的生命财产安全至关重要。然而&#xff0c;在大多数情况下&#xff0c;火灾往往在被发现时已经进展到了难以控制的阶段&#xff0c;这导致了严重的后果。为了解决这一问题&#xff0c;我们提出了基于深度学习技术的火灾烟雾检测系…

前端开发攻略---介绍HTML中的<dialog>标签,浏览器的原生弹框。

1、演示 2、介绍 <dialog> 标签用于定义对话框&#xff0c;即一个独立的窗口&#xff0c;通常用来显示对话框、提示框、确认框等弹出式内容。在对话框中&#xff0c;可以包含文本、表单元素、按钮等内容&#xff0c;用户可以和这些内容进行交互。 3、兼容性 4、示例代码 …

03-MVC执行流程-参数解析与Model

重要组件 准备Model&#xff0c;Controller Configuration public class WebConfig {ControllerAdvicestatic class MyControllerAdvice {ModelAttribute("b")public String bar() {return "bar";}}Controllerstatic class Controller1 {ResponseStatus(H…

现代永磁同步电机控制原理pdf及全套matlab仿真模型

现代永磁同步电机控制原理pdf及matlab仿真模型。全书包含SVPWM, DTC, Lun, smo, EKF, HFI等经典控制算法。将书中10章节涉及到的模型复原搭建模型。 模型获取链接&#xff1a;现代永磁同步电机控制原理pdf及全套matlab仿真模型

电商API数据采集接口||大数据的发展,带动电子商务产业链,促进了社会的进步

最近几年计算机技术在诸多领域得到了有效的应用&#xff0c;同时在多方面深刻影响着我国经济水平的发展。除此之外&#xff0c;人民群众的日常生活水平也受大数据技术的影响。 主流电商API数据采集接口||在这其中电子商务领域也在大数据技术的支持下&#xff0c;得到了明显的进…

windows环境下DVWA靶场搭建

目录 一&#xff0c;安装PHPstudy 二&#xff0c;DVWA靶场上传 一&#xff0c;安装PHPstudy 具体安装步骤&#xff0c;请看上篇文章https://blog.csdn.net/m0_72210904/article/details/138258609?spm1001.2014.3001.5501 二&#xff0c;DVWA靶场上传 压缩包&#xff1a;&…

计算机混合运算(计算器)

学习贺利坚老师,计算机混合运算, 做出处理混合运算 数据结构例程——表达式求值&#xff08;用栈结构&#xff09;_用栈表达式求值-CSDN博客文章浏览阅读1.5w次&#xff0c;点赞14次&#xff0c;收藏61次。本文针对数据结构基础系列网络课程(3)&#xff1a;栈和队列中第5课时栈…

工业异常检测

工业异常检测在业界和学界都一直是热门&#xff0c;近期其更是迎来了全新突破&#xff1a;与大模型相结合&#xff01;让异常检测变得更快更准更简单&#xff01; 比如模型AnomalyGPT&#xff0c;它克服了以往的局限&#xff0c;能够让大模型充分理解工业场景图像&#xff0c;判…

与 Apolo 共创生态:Apolo 7周年大会我的启示与心得分享

文章目录 前言一、Apolo加速企业自动驾驶解决方案落地二、应用X企业预制套件&#xff1a;启示与心得三、Studio X企业协同工具链&#xff1a;共创心得分享 前言 本篇文章我将围绕Apolo 7周年来给大家分享我所学习到的内容和一些心得。 Apolo 7周年活动链接&#xff1a;https:…

2024年【陕西省安全员C证】报名考试及陕西省安全员C证考试资料

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年陕西省安全员C证报名考试为正在备考陕西省安全员C证操作证的学员准备的理论考试专题&#xff0c;每个月更新的陕西省安全员C证考试资料祝您顺利通过陕西省安全员C证考试。 1、【多选题】下列做法正确的有&#…

模块化兼容性

模块化兼容性 由于webpack同时支持CommonJS和ES6 module&#xff0c;因此需要理解它们互操作时webpack是如何处理的 同模块化标准 如果导出和导入使用的是同一种模块化标准&#xff0c;打包后的效果和之前学习的模块化没有任何差异 不同模块化标准 不同的模块化标准&#x…

Vue入门到关门之计算属性与监听属性

一、计算属性 1、什么是计算属性 计算属性是基于其它属性计算得出的属性&#xff0c;就像Python中的property&#xff0c;可以把方法/函数伪装成属性&#xff0c;在模板中可以像普通属性一样使用&#xff0c;但它们是基于响应式依赖进行缓存的。这意味着只有在依赖的响应式数…

jsp servlet 学生信息管理系统

一、角色划分 1、超级管理员 2、学生 二、模块展示 1、登录 2、列表页面【超级管理员展示所有用户信息、学生只展示当前登录用户信息】 3、新增 4、编辑 三、数据库【mysql】 四、运行演示 jsp servlet 学生信息管理系统