《Linux C编程实战》笔记:消息队列

news2025/1/8 16:02:52

消息队列是一个存放在内核中的消息链表,每个消息队列由消息队列标识符标识。与管道不同的是消息队列存放在内核中,只有在内核重启(即操作系统重启)或显示地删除一个消息队列时,该消息队列才会被真正的删除。

操作消息队列时,需要用到一些数据结构,熟悉这些数据结构是掌握消息队列的关键。下面介绍几个重要的数据结构

消息缓冲结构

向消息队列发送消息时,必须组成合理的数据结构。Linux系统定义了一个模板数据结构msgbuf

#include<linux/msg.h>
struct msgbuf {
	long mtype;          /* type of message */
	char mtext[1];                  /* message text */
};

结构体中的mtype字段代表消息类型。给消息指定类型,可以使得消息在一个队列中重复使用。mtext字段指消息内容。

mtext最然定义为char类型,并不代表消息只能是一个字符,消息内容可以为任意类型,由用户根据需要定义,如下面就是用户定义的一个消息结构

struct myMsgbuf{
    long mtype;
    struct student std;
}

消息队列中的消息的大小是受限制的,由<linux/msg.h>中的宏MSGMAX给出消息的最大长度,在实际应用中要注意这个限制。

msqid_ds内核数据结构

Linux内核中,每个消息队列都维护一个结构体msqid_qs,此结构体保存着消息队列当前的状态信息。该结构体定义在头文件linux/msg.h中,具体如下

struct msqid_ds {
	struct ipc_perm msg_perm;
	struct msg *msg_first;		/* first message on queue,unused  */
	struct msg *msg_last;		/* last message in queue,unused */
	__kernel_old_time_t msg_stime;	/* last msgsnd time */
	__kernel_old_time_t msg_rtime;	/* last msgrcv time */
	__kernel_old_time_t msg_ctime;	/* last change time */
	unsigned long  msg_lcbytes;	/* Reuse junk fields for 32 bit */
	unsigned long  msg_lqbytes;	/* ditto */
	unsigned short msg_cbytes;	/* current number of bytes on queue */
	unsigned short msg_qnum;	/* number of messages in queue */
	unsigned short msg_qbytes;	/* max number of bytes on queue */
	__kernel_ipc_pid_t msg_lspid;	/* pid of last msgsnd */
	__kernel_ipc_pid_t msg_lrpid;	/* last receive pid */
};
  1. msg_perm:用于存储IPC对象的权限信息以及队列的用户ID,组ID等信息。
  2. msg_first:队列中的第一个消息的指针。
  3. msg_last:队列中的最后一个消息的指针。
  4. msg_stime:上次发送消息的时间。
  5. msg_rtime:上次接收消息的时间。
  6. msg_ctime:上次修改消息队列的时间。
  7. msg_lcbytes:用于32位系统的字段,未使用。
  8. msg_lqbytes:用于32位系统的字段,未使用。
  9. msg_cbytes:当前队列中消息的总字节数。
  10. msg_qnum:队列中当前存在的消息数量。
  11. msg_qbytes:队列中允许的最大字节数。
  12. msg_lspid:最后一次发送消息的进程ID。
  13. msg_lrpid:最后一次接收消息的进程ID。

ipc_perm内核数据结构

结构体ipc_perm保存着消息队列的一些重要信息,比如消息队列关联的键值,消息队列的用户ID,组ID等,它定义在头文件linux/ipc.h中

struct ipc_perm
{
	__kernel_key_t	key;
	__kernel_uid_t	uid;
	__kernel_gid_t	gid;
	__kernel_uid_t	cuid;
	__kernel_gid_t	cgid;
	__kernel_mode_t	mode; 
	unsigned short	seq;
};
  1. key:创建消息队列用到的键值key。
  2. uid:消息队列的用户ID(User ID)。
  3. gid:消息队列的组ID(Group ID)。
  4. cuid:创建消息队列的用户ID(User ID)。
  5. cgid:创建消息队列的组ID(Group ID)。
  6. mode:权限模式(Permission Mode),包括读、写、执行权限等。
  7. seq:序列号,用于确保唯一性和顺序性。

消息队列的创建与读写

创建消息队列

消息队列是随着内核的存在而存在的,每个消息队列在系统范围内对应唯一的键值。要获得一个消息队列的描述符,只需要提供该消息队列的键值即可,该键值通常通过ftok函数返回。该函数定义在头文件sys/ipc.h中

#include<sys/types.h>
#include<sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);

ftok 函数通过将给定的文件路径名与给定的项目ID进行哈希运算生成一个唯一的键值。失败返回-1.

示例代码1

演示ftok的使用

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
int main(int argc,char **argv,char **environ){
    for(int i=1;i<=5;i++)
        printf("key[%d] = %u \n",i,ftok(".",i));
    return 0;
}

根据路径名和proj_id就会得到一个键值。

注意:参数pathname在系统中一定要存在且进程有权访问,参数proj_id的取值范围为1-255

ftok返回的键值可以提供给函数msgget。msgget根据这个键值创建一个新的消息队列或者访问一个已经存在的消息队列。msgget定义在头文件sys/msg.h中。

int msgget(key_t key, int msgflg);

该函数接受两个参数:

  1. key:消息队列的键值,通常使用 ftok 函数生成。它用于标识消息队列。

  2. msgflg:用于指定消息队列的创建和访问权限的标志。通常情况下,它可以是以下几个值的按位或组合:

    • IPC_CREAT:如果消息队列不存在,则创建一个新的消息队列。
    • IPC_EXCL:与 IPC_CREAT 一起使用时,如果消息队列已经存在,则返回错误。
    • 权限掩码:用于设置消息队列的访问权限,例如 0666

msgget 函数返回一个标识符,它是消息队列的唯一标识符。如果成功,返回值是一个非负整数;如果失败,返回值为 -1,并设置 errno 来指示错误的原因。

写消息队列

函数msgsnd用于向消息队列发送数据。该函数定义在头文件sys/msg.h中

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

该函数接受四个参数:

  1. msqid:消息队列的标识符,通常由 msgget 函数返回。

  2. msgp:指向消息数据的指针,通常是一个用户定义的结构体指针,用于存储要发送的数据。

  3. msgsz:消息的大小,以字节为单位。该值应该是 msgp 指向的数据结构的大小。

  4. msgflg:用于指定消息发送的行为标志,可以是以下之一或它们的按位或组合:

    • IPC_NOWAIT:如果消息队列已满,则立即返回,而不是等待空闲空间。
    • 0:默认行为,如果消息队列已满,则进程将被阻塞,直到有足够的空间将消息发送到队列中。

msgsnd 函数返回一个整数值,如果成功,返回值为 0;如果失败,返回值为 -1,并设置 errno 来指示错误的原因。

使用 msgsnd 函数时,需要确保指定的消息队列标识符有效,并且消息队列具有足够的空间来容纳要发送的消息。

示例程序2

演示往消息队列发消息

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#define BUF_SIZE 256
#define PROJ_ID 32
#define PATH_NAME "."
int main(int argc,char **argv,char **environ){
    //用户自定义消息缓存
    struct mymsgbuf{
        long msgtype;
        char ctrlstring[BUF_SIZE];
    }msgbuffer;
    int qid;
    int msglen;
    key_t msgkey;
    //获取键值
    if((msgkey=ftok(PATH_NAME,PROJ_ID))==-1){
        perror("ftok error!\n");
        exit(1);
    }
    //创建消息队列
    if((qid=msgget(msgkey,IPC_CREAT|0660))==-1){
        perror("msgget error!\n");
        exit(1);
    }
    //填充消息结构,发送到消息队列
    msgbuffer.msgtype=3;
    strcpy(msgbuffer.ctrlstring,"hello,message queue");
    msglen=sizeof(struct mymsgbuf)-4;
    if(msgsnd(qid,&msgbuffer,msglen,0)==-1){
        perror("msgget error!\n");
        exit(1);
    }
    exit(0);
}

ipcs 命令是用于列出当前系统上的 System V IPC(Inter-Process Communication)资源的信息,包括消息队列、信号量和共享内存。

从结果看,系统内部生成了一个消息队列,其中含有一条消息。

读消息队列

消息队列中放入数据后,其他进程就可以读取其中的信息了。读取消息队列的函数是msgrcv,该函数定义在头文件sys/msg.h中

#include <sys/msg.h>

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
  1. msqid:消息队列的标识符,用于指定从哪个消息队列接收消息。
  2. msgp:一个指向消息缓冲区的指针,用于存储接收到的消息。
  3. msgsz:消息缓冲区的大小,即 msgp 指向的缓冲区的大小。
  4. msgtyp:指定要接收的消息类型。如果 msgtyp 为正数,则函数将接收第一个消息类型为 msgtyp 的消息。如果 msgtyp 为 0,则接收队列中的第一个消息,无论其类型是什么。如果 msgtyp 为负数,则接收队列中类型值最小且小于等于 msgtyp 绝对值的消息。
  5. msgflg:附加标志,用于控制函数的行为。可以使用 IPC_NOWAIT(如果没有可用消息,则立即返回)和/或 MSG_NOERROR(如果消息大于 msgsz,则截断消息)的组合。
  6. 如果函数成功接收到消息,则返回接收到的消息的字节数量。
  7. 如果函数调用失败,则返回 -1,并设置 errno 来指示错误的类型。

如果不设置IPC_NOWAIT的话,msgrcv也会阻塞进程

示例程序3

下面的例子就来读取之前发送的消息

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#define BUF_SIZE 256
#define PROJ_ID 32
#define PATH_NAME "."
int main(int argc,char **argv,char **environ){
    struct mymsgbuf{
        long msgtype;
        char ctrlstring[BUF_SIZE];
    }msgbuffer;
    int qid;
    int msglen;
    key_t msgkey;
    if((msgkey=ftok(PATH_NAME,PROJ_ID))==-1){
        perror("ftok error!\n");
        exit(1);
    }
    if((qid=msgget(msgkey,IPC_CREAT|0660))==-1){
        perror("msgget error!\n");
        exit(1);
    }
    //前面的都一样
    msglen=sizeof(struct mymsgbuf)-4;
    //第四个参数为什么是3呢,因为我们发的时候设置的就是3
    if(msgrcv(qid,&msgbuffer,msglen,3,0)==-1){
        perror("msgrcv error!\n");
        exit(1);
    }
    printf("Get message %s \n",msgbuffer.ctrlstring);
    exit(0);
}

因为给 ftok提供的参数是一样的,所以能得到对应的消息队列。因为我们已经创建了该消息队列,所以msgget就只是得到qid。

获取和设置消息队列的属性

上面介绍到了消息队列的属性保存在数据结构msqid_ds中,用户可以通过函数msgctl获取或设置消息队列的属性。msgctl定义在头文件sys/msg.h中

int msgctl(int msqid, int cmd, struct msqid_ds *buf);
  • msqid 是消息队列的标识符,由msgget函数返回。
  • cmd 是命令,指定了你想执行的操作。常见的命令包括:
    • IPC_STAT:获取消息队列的当前状态。
    • IPC_SET:设置消息队列的属性。
    • IPC_RMID:删除消息队列。
  • buf 是一个指向msqid_ds结构的指针,用于存储或传递消息队列的状态信息。

示例程序4

演示如何获取和设置消息队列的属性

#include<stdio.h>
#include<stdlib.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<sys/types.h>
#include<string.h>
#include<time.h>
#define BUF_SIZE 256
#define PROJ_ID 32
#define PATH_NAME "."
void getmsgattr(int msgid,struct msqid_ds msg_info);
int main(void){
    //自定义的消息缓冲区
    struct mymsgbuf{
        long msgtype;
        char ctrlstring[BUF_SIZE];
    }msgbuffer;
    int qid;
    int msglen;
    key_t msgkey;
    struct msqid_ds msg_attr;
    //获取键值
    if((msgkey=ftok(PATH_NAME,PROJ_ID))==-1){
        perror("ftok error!\n");
        exit(1);
    }
    //获取下奥西队列的标识符
    if((qid=msgget(msgkey,IPC_CREAT|0666))==-1){
        perror("msgget error!\n");
        exit(1);
    }
    getmsgattr(qid,msg_attr);//输出属性

    //发送一条消息到消息队列
    msgbuffer.msgtype=2;
    strcpy(msgbuffer.ctrlstring,"Aother message");
    msglen=sizeof(struct mymsgbuf)-4;
    if(msgsnd(qid,&msgbuffer,msglen,0)==-1){
        perror("msgget error!\n");
        exit(1);
    }
    getmsgattr(qid,msg_attr);//再次查看消息队列的属性

    //这次手动设置消息队列的属性
    msg_attr.msg_perm.uid=3;
    msg_attr.msg_perm.gid=2;
    if(msgctl(qid,IPC_SET,&msg_attr)==-1){//用msgctl函数修改
        perror("msg set error!\n");
        exit(1);
    }
    getmsgattr(qid,msg_attr);//输出修改后的属性

    //删除消息队列
    if(msgctl(qid,IPC_RMID,NULL)==-1){
        perror("delete msg error!\n");
        exit(1);
    }
    getmsgattr(qid,msg_attr);//删除后再观察属性
    exit(0);
}
void getmsgattr(int msgid,struct msqid_ds msg_info){
    if(msgctl(msgid,IPC_STAT,&msg_info)==-1){//通过msgctl获取消息队列的属性
        perror("msgctl error!\n");
        return;
    }
    //输出的内容参照前文对struct msqid_ds结构体的介绍
    printf("****information of message queue%d****\n",msgid);
    printf("last msgsnd to msg time is %s\n",ctime(&(msg_info.msg_stime)));
    printf("last msgrcv time from msg is %s\n",ctime(&(msg_info.msg_rtime)));
    printf("last change msg time is %s\n",ctime(&(msg_info.msg_ctime)));
    printf("current number of bytes on queue is %d\n",msg_info.__msg_cbytes);
    printf("number of messages in queue is %d\n",msg_info.msg_qnum);
    printf("max number of bytes on queue is %d\n",msg_info.msg_qbytes);
    printf("pid of last msgsnd is %d\n",msg_info.msg_lspid);
    printf("pid of last msgrcv is %d\n",msg_info.msg_lrpid);

    printf("msg uid is %d\n",msg_info.msg_perm.uid);
    printf("msg gid is %d",msg_info.msg_perm.gid);
    printf("****infromation end!****\n");
}

执行结果的片段,因为太长截不下。

getmsgattr函数好像不需要第二个参数啊,直接在函数内部声明一个临时的就行,反正都是从消息队列里重新获取的。不知道书上为什么这么写。

示例程序5

上一节使用了有名管道实现了服务器和客户进程的通信,这回使用消息队列来完成。

server端:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/stat.h>
#include<sys/msg.h>
#define BUF_SIZE 256
#define PROJ_ID 32
#define PATH_NAME "."
#define SERVER_MSG 1
#define CLIENT_MSG 2    
int main(int argc,char **argv,char **environ){
    struct mymsgbuf{
        long msgtype;
        char ctrlstring[BUF_SIZE];
    }msgbuffer;
    int qid;
    int msglen;
    key_t msgkey;
    if((msgkey=ftok(PATH_NAME,PROJ_ID))==-1){
        perror("ftok error!\n");
        exit(1);
    }
    if((qid=msgget(msgkey,IPC_CREAT|0660))==-1){
        perror("msgget error!\n");
        exit(1);
    }
    //前面基本都一样,就是获取键值,获取消息队列的qid
    while(1){
        printf("server: ");
        fgets(msgbuffer.ctrlstring,BUF_SIZE,stdin);//从标准输入读
        if(strncmp("exit",msgbuffer.ctrlstring,4)==0){//如果是exit则删除消息队列退出
            msgctl(qid,IPC_RMID,NULL);
            break;
        }
        msgbuffer.ctrlstring[strlen(msgbuffer.ctrlstring)-1]='\0';
        msgbuffer.msgtype=SERVER_MSG;//标记是来自服务器端的信息
        if(msgsnd(qid,&msgbuffer,strlen(msgbuffer.ctrlstring)+1,0)==-1){//+1是因为还有\0
            perror("Server msgsnd error!\n");
            exit(1);
        }
        if(msgrcv(qid,&msgbuffer,BUF_SIZE,CLIENT_MSG,0)==-1){//接收来自客户端的信息
            perror("Server msgrcv error!\n");
            exit(1);
        }
        printf("Clinet:%s\n",msgbuffer.ctrlstring);
    }
    exit(0);
}

这里要解释一下msgbuffer.ctrlstring[strlen(msgbuffer.ctrlstring)-1]='\0';这一句代码

fgets会保存输入时的换行符,也就是说整个字符串是包括一个\n的,当然最后也包括\0,但是我们发送的信息不需要这个换行符,所以这句代码的意思就是把换行符\n改成\0。因为strlen是计算从开始到\0之前的长度,比如说一个字符串是 abc\n\0,那么strlen得到的是4,\n的下标位置是3.

client端:程序基本就是改了个顺序,先接收来自服务器端的数据,再发送数据

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/stat.h>
#include<sys/msg.h>
#define BUF_SIZE 256
#define PROJ_ID 32
#define PATH_NAME "."
#define SERVER_MSG 1
#define CLIENT_MSG 2    
int main(int argc,char **argv,char **environ){
    struct mymsgbuf{
        long msgtype;
        char ctrlstring[BUF_SIZE];
    }msgbuffer;
    int qid;
    int msglen;
    key_t msgkey;
    if((msgkey=ftok(PATH_NAME,PROJ_ID))==-1){
        perror("ftok error!\n");
        exit(1);
    }
    if((qid=msgget(msgkey,IPC_CREAT|0660))==-1){
        perror("msgget error!\n");
        exit(1);
    }
    while(1){
        if(msgrcv(qid,&msgbuffer,BUF_SIZE,SERVER_MSG,0)==-1){
            perror("Server msgrcv error!\n");
            exit(1);
        }
        printf("server:%s\n",msgbuffer.ctrlstring);
        printf("client: ");
        fgets(msgbuffer.ctrlstring,BUF_SIZE,stdin);
        if(strncmp("exit",msgbuffer.ctrlstring,4)==0){
            break;
        }
        msgbuffer.ctrlstring[strlen(msgbuffer.ctrlstring)-1]='\0';
        msgbuffer.msgtype=CLIENT_MSG;
        if(msgsnd(qid,&msgbuffer,strlen(msgbuffer.ctrlstring)+1,0)==-1){
            perror("client msgsnd error!\n");
            exit(1);
        }

    }
    exit(0);
}

先运行server程序,再在一个终端运行client程序,这样两个程序之间就可以进行聊天

这篇写了真的很久,内容超级多而且很杂,内容又很新,我自己学习也花了很久

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

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

相关文章

前端进度条组件NProgress

nprogress 安装 npm install --save nprogress使用 import NProgress from nprogress // 引入nprogress插件 import nprogress/nprogress.css // 这个nprogress样式必须引入// axios请求拦截器 axios.interceptors.request.use(config > {NProgress.start() // 设置加载进…

二维码扫码登录原理,其实比你想的要简单的多

二维码&#xff0c;大家再熟悉不过了 购物扫个码&#xff0c;吃饭扫个码&#xff0c;坐公交也扫个码 在扫码的过程中&#xff0c;大家可能会有疑问&#xff1a;这二维码安全吗&#xff1f; 会不会泄漏我的个人信息&#xff1f; 更深度的用户还会考虑&#xff1a;我的系统是不…

【合宙ESP32C3 Arduino开发】第一篇:初探合宙ESP32C3

忘记过去&#xff0c;超越自己 ❤️ 博客主页 单片机菜鸟哥&#xff0c;一个野生非专业硬件IOT爱好者 ❤️❤️ 本篇创建时间 2024-02-21❤️❤️ 本篇更新时间 2024-02-21❤️&#x1f389; 欢迎关注 &#x1f50e;点赞 &#x1f44d;收藏 ⭐️留言&#x1f4dd;&#x1f64f;…

时序分析深入必学的时序模型详细讲解

目录 一、前言 二、时序建模 2.1 反相器 2.2 线性时序模型 2.3 非线性时序模型 三、时序模块 3.1 组合单元 3.2 时序单元 3.3 同步检查&#xff1a;setup和hold 3.4 异步检查&#xff1a;recovery和removal 3.5 脉冲宽度检查Pulse width check 3.6 传输时延 3.7 …

yolov5-tracking-xxxsort yolov5融合六种跟踪算法(一)--环境配置GPU版本

本次开源计划主要针对大学生无人机相关竞赛的视觉算法开发。 开源代码仓库链接&#xff1a;https://github.com/zzhmx/yolov5-tracking-xxxsort.git 如果要配置CPU环境可以看我这篇文章&#xff1a; yolov5-tracking-xxxsort yolov5融合六种跟踪算法&#xff08;一&#xff09;…

【安卓基础2】简单控件

&#x1f3c6;作者简介&#xff1a;|康有为| &#xff0c;大四在读&#xff0c;目前在小米安卓实习&#xff0c;毕业入职。 &#x1f3c6;安卓学习资料推荐&#xff1a; 视频&#xff1a;b站搜动脑学院 视频链接 &#xff08;他们的视频后面一部分没再更新&#xff0c;看看前面…

[java基础揉碎]this

引出this: 什么是this: java虚拟机会给每个对象分配 this&#xff0c;代表当前对象。 这里的this就是new出来的这个对象 this的本质: this是个引用在堆中指向它自己: this的细节: 访问成员方法: 访问构造器:

【GUI编程】Tkinter之OptionMenu

OptionMenu OptionMenu类是一个辅助类&#xff0c;它用来创建弹出菜单&#xff0c;并且有一恶搞按钮显示它。它非常类似Windows上的下拉列表插件。 如果要获取当前选项菜单的值&#xff0c;你需要把它和一个Tkinter变量联系起来。 def __init__(self, master, variable, val…

SQL防止注入工具类,可能用于SQL注入的字符有哪些

SQL注入是一种攻击技术&#xff0c;攻击者试图通过在输入中注入恶意的SQL代码来干扰应用程序的数据库查询。为了防止SQL注入&#xff0c;你需要了解可能用于注入的一些常见字符和技术。以下是一些常见的SQL注入字符和技术&#xff1a; 单引号 ​&#xff1a; 攻击者可能会尝试…

中国 AI 开课速度直逼美国 AI 颠覆性创新速度

原文链接&#xff1a; 中国 AI 开课速度直逼美国 AI 颠覆性创新速度 今日热帖&#xff0c;有网友发帖称&#xff1a;Sora 和 ChatGPT 告诉我们&#xff0c;美国确实是遥遥领先&#xff0c;而且还越拉越远。 是不是遥遥领先暂且不说&#xff0c;但领先我们的确是事实。 主要是…

尚未创建默认 SSL 站点。若要支持不带 SNI 功能的浏览器,建议创建一个默认 SSL 站点。

在 Windows Server 2012 IIS 站点中设置 SSL 证书后&#xff0c;IIS 右上角提示&#xff1a; 尚未创建默认 SSL 站点。若要支持不带 SNI 功能的浏览器&#xff0c;建议创建一个默认 SSL 站点。 该提示客户忽略不管&#xff0c;但是若要支持不带 SNI(Server Name Indication)…

消息中间件-面试题

MQ选择 一、Kafka 1、消息队列如何保证消息可靠性 消息不重复 生产者控制消费者幂等消息不丢失 生产者发送,要确认broker收到并持久化broker确认消费者消费完,再删除消息2、kafka是什么 Kafka是一种高吞吐量、分布式、基于发布/订阅的消息中间件,是Apache的开源项目。broke…

多线程——threading和queue模块的理解。加实例+详解+思路

并发&#xff1a;假的多任务 并行&#xff1a;真的多任务 实现多线程用——threading模块 import threading import timedef shuru():for i in range(1,4):print("正在输入")time.sleep(1) def shuchu():for i in range(1,4):print("正在输出")time.sle…

初阶数据结构之---顺序表和链表(C语言)

引言-线性表 线性表&#xff1a; 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构。线性表在逻辑上是线性结构&#xff0c;也就是说是连续的一条直线。但在物理上并不一定是连续的。线性表在物理上…

基于EasyCVR视频汇聚系统的公安网视频联网共享视频云平台建设思路分析(一)

随着社会的发展和科技的进步&#xff0c;视频监控系统在各个领域的应用越来越广泛&#xff0c;视频云平台建设已经成为了行业数字化转型的重要一环。公安网视频汇聚联网共享云的建设需要充分考虑技术、架构、安全、存储、计算等多方面因素&#xff0c;以确保平台的稳定性和可用…

深度学习图像处理基础

这里写目录标题 分辨率是什么 视网膜屏视网膜屏人眼的视觉视力 像素密度设置合适的PPI&#xff0c;制造视网膜屏 色彩是什么色匹配实验色彩匹配的意义量化色彩匹配白色合为1色度图 总结 HDR光亮度&#xff08;尼特&#xff09;灰阶亮度范围HDR显示技术总结 数字图像化概览 人脸…

Excel SUMPRODUCT函数用法(乘积求和,分组排序)

SUMPRODUCT函数是Excel中功能比较强大的一个函数&#xff0c;可以实现sum,count等函数的功能&#xff0c;也可以实现一些基础函数无法直接实现的功能&#xff0c;常用来进行分类汇总&#xff0c;分组排序等 SUMPRODUCT 函数基础 SUMPRODUCT函数先计算多个数组的元素之间的乘积…

German Prepositions

German Prepositions 一, ab and auerhalb1, ab2,auerhalb 二, an三&#xff0c;auf 一, ab and auerhalb 1, ab 2,auerhalb 二, an 静三 动四 时间&#xff08;节日省略dem&#xff09; 表近似 三&#xff0c;auf 静三 动四 meas wann 加一段时间 表方式 固定搭…

若依前后端分离版如何集成的mybatis以及修改集成mybatisplus实现Mybatis增强

场景 若依前后端分离版手把手教你本地搭建环境并运行项目&#xff1a; 若依前后端分离版手把手教你本地搭建环境并运行项目_本地运行若依前后端分离-CSDN博客 SpringBoot中使用PageHelper插件实现Mybatis分页&#xff1a; SpringBoot中使用PageHelper插件实现Mybatis分页-C…

本地创建Git仓库

在 Windows 下&#xff0c;可以通过以下步骤在本地创建一个 并模拟远程Git 仓库。 1、在命令行中打开模拟远程Git 仓库目标文件夹&#xff1a; 打开命令提示符或 PowerShell。例如&#xff1a; 创建裸仓库&#xff08;模拟远程仓库&#xff09;&#xff1a;创建一个裸仓库&am…