C++ Webserver从零开始:基础知识(七)——多进程编程

news2025/1/31 8:26:40

前言

        在学习操作系统时,我们知道现代计算机往往都是多进程多线程的,多进程和多线程技术能大大提高了CPU的利用率,因此在web服务器的设计中,不可避免地要涉及到多进程多线程技术。

        这一章将简要讲解web服务器中的多进程编程,本文不会很详细,也不会在原理性的知识上多费笔墨。如果读者有什么不理解的地方,建议学习一下操作系统的基础知识。


fork系统调用

#include<sys/types.h>
#include<unistd.h>
pid_t fork(void);
  • 作用:复制当前进程,在内核进程表创建一个新的表项。新表项许多属性与原进程相同,比如堆指针,栈指针和标志寄存器的值。新进程的PPID为原进程的PID,信号位图被清除(原进程的信号处理函数对新进程不起作用;
  • 参数:无
  • 返回值(两次):一般根据fork返回值判断正在执行这段代码的是新进程还是原进程
    • 父进程中:返回子进程的PID
    • 子进程中:返回0

fork的一些注意事项:

  1. 子进程的代码与父进程完全相同
  2. 子进程采用写时复制的方式复制父进程的数据(堆数据,栈数据和静态数据)
  3. 父进程打开的文件描述符在子进程中同样打开,每fork一次文件描述符的全局引用+1


exec系统调用

        上面的fork是复制出一个新进程,类似于ctrl + c 和 ctrl + v,而exec系统调用则类似于 ctrl + x, ctrl + v。

#include<unistd.h>
extern char** environ;//设置新程序的环境变量
int execl(const char* path, const char* arg, ...);
int execlp(const char* file, const char* arg, ...);
int execle(const char* path, const char* arg, ..., char* const envp[]);
int execv(const char* path, const char* argv[]);
int execvp(const char* file, const char* arg[]);
int execve(const char* path, const char* arg[], char* const envp[]);
  • 作用:将当前进程替换成另一个进程并执行
  • 参数:
    • path: 指定可执行文件的完整路径
    • file:接收文件名,该文件的具体位置在环境遍历path中搜寻
    • arg:接收可变参数(被传递给新程序的main)
    • argv:接收参数数组(被传递给新程序的main)
    • envp[ ]:设置新程序的环境变量,若未设置则环境变量由environ指定
  • 返回值:一般不返回(这是因为当exec成功执行后,原程序的代码不会执行,返回值也就没用了)
    • 失败:-1

PS:exec不会关闭原程序打开的文件描述符


僵尸进程

        多进程中父进程一般需要跟踪子进程的退出状态,所以子进程退出时内核一般不会立刻释放其资源。这会出现以下两种情况:

  1. 子进程运行结束了,父进程还未读取其状态
  2. 父进程异常终止了,子进程继续运行直至结束,守护进程还未释放其资源
    1. 这时子进程被称为孤儿进程,孤儿进程会被守护进程init(PID为1)所收养,即其PPID被设为1
    2. 守护进程init会等待孤儿进程结束并释放其资源

        处于以上两种状态的僵尸进程,僵尸进程会占据内核的资源却不行使任何功能,所以我们要避免僵尸进程。方法是wait调用释放僵尸进程.

#include<sys/type.h>
#include<sys/wait.h>
pid_t wait(int* stat_loc);
pid_t waitpid(pid_t pid, int* stat_loc, int options);
  • 作用:等待子进程运行结束释放其资源
  • 参数
    • stat_loc(两个函数调用相同):指向一块内存,该内存存储子进程的退出信息
    • pid : 要求释放的子进程
    • options : 控制waitpid的行为,常用参数为WNOHANG(设置函数为非阻塞)
  • 返回值:
    • wait
      • 成功:返回结束运行的子进程的PID
      • 失败:-1
    • waitpid(非阻塞):
      • 成功:
        • pid指定的子进程还没结束或意外终止:0
        • pid指定的子进程正常退出:该子进程的PID
      • 失败:-1

        很显然这里有一个问题,当使用waitpid函数调用时是非阻塞的,既然是非阻塞的我们就得在得知子进程结束之后再调用它才合理,那么如何得知子进程结束了呢:

        答案是:使用SIGCHLD信号,该信号由子进程结束时给父进程发送,父进程在收到这个信号后即可在信号处理函数中调用非阻塞waitpid

static void handle_child(int sig) {
    pid_t pid;
    int stat;
    while ((pid == waitpid(-1 , &stat, WNOHANG)) > 0 ) {
        /*处理结束的子进程*/
    }
}


管道

        通过以上三节,我们学会了创建子进程和新进程,并学会了释放僵尸进程。接下来我们学习如何在进程之间通信,其中最简单的通信方式即管道。

        在C++ Webserver从零开始:基础知识(一)——Linux网络编程基础API-CSDN博客中我们介绍了管道使用的API,这里不再赘述,就简要介绍进程之间管道使用的注意事项

        我们知道管道由两个文件描述符组成,分别为fd[0]和fd[1]。在fork后两个文件描述符都是打开状态,而又因一个pipe管道只能实现一个方向的数据传输,因此父进程和子进程必须一个关掉fd[0],一个关掉fd[1]。

        当然,如果想要实现双向的传输,则需要创建两个管道pipe,在通信中我们称其为全双工管道。

        实现全双工管道还可以使用socketpair系统调用。

        管道通信的弊端:

        管道通信只能是两个关联进程(如父进程和子进程)之间进行通信,而要多个不相关的进程之间通信,则需要使用命名管道FIFO,本文不予介绍。


信号量

信号量原语

        在学习操作系统的时候相信大家都了解了信号量,这里简单介绍一下。

        当多个程序需要访问系统上的某一个资源时(通常为很短的一段代码,称为临界区),为了避免竞态条件,我们需要让同一时间只有一个进程进入临界区,这时就需要使用信号量来加以限制。

        信号量的原理类似于一把锁,临界区类似于一个房间内的资源,当有进程需要使用临界区的资源时,就把房间上锁再使用,这样当其他进程需要用时就只能在房外等待。当使用完毕后,进程需要把锁解开,这样下一个进程就可以进入临界区。

        操作系统中对信号量操作一般称为P,V操作,其中P为上锁,V为释放锁。

Linux中,信号量的API定义在sys/sem.h头文件中。主要包括以下三个变量:

  • semget
  • semop
  • semctl

semget系统调用

#include<sys/sem.h>
int semget(key_t key, int num_sems, int sem_flags);
  • 作用:用于创建一个新的信号量集,或获取一个已经存在的信号量集
  • 参数:
    • key:键值,用来标识全局唯一的信号量集,通过信号量通信的进程需要使用相同的键值创建/获取该信号量。
    • num_sems:指定要创建的信号量集中数量的数目(如果是获取信号量集,则设为0即可)
    • sem_flas:指定一组标志来控制该API的细节,其具体格式和含义与系统调用open的mode参数相同
  • 返回值:
    • 成功:信号量集的标识符
    • 失败:-1

注意,该系统调用有两个作用:

  1. 创建一个信号量集
  2. 获得一个已经存在的信号量集

当系统调用是第一个作用时,会将一个相关联的内核数据结构体semid_ds初始化

#include<sys/sem.h>
/*该结构体用于描述IPC对象(信号量,共享内存和消息队列)的权限*/
struct ipc_perm{
    key_t key;/*键值*/
    uid_t uid;/*所有者的有效用户id*/
    gid_t gid;/*所有者的有效组id*/
    uid_t cuid;/*创建者的有效用户id*/
    git_t cgid;/*创建者的有效组id*/
    mode_t mode;/*访问权限*/
    /*省略其他*/
}
struct semid_ds{ 
    struct ipc_perm sem_perm;/*信号量的操作权限*/
    unsigned long int sem_nsems;/*该信号量集中的信号量数目*/
    time_t sem_otime;/*最后一次调用semop的时间*/
    time_t sem_ctimel;/*最后一次调用semctl的时间*/
    /*省略其他*/
}

初始化的具体数值可自行搜索。

semop系统调用

semop系统调用改变信号量的值,即执行P,V操作。具体的PV操作实际上是对以下内核变量进行操作:

unsigned short semval;/*信号量的值*/
unsigned short semzcnt;/*等待信号量值变成0的进程数量*/
unsigned short semncnt;/*等待信号量值增加的进程数量*/
pid_t sempid;/*最后一次执行semop操作的进程id*/

以下是semop系统调用:

#include<sys/sem.h>
int semop(int sem_id, struct sembuf* sem_ops, size_t num_sem_ops);
  • 作用:改变信号量的值,执行PV操作
  • 参数:
    • sem_id:semget调用返回的信号量集标识符
    • sem_ops:一个sembuf结构体类型的数组,见下文单独介绍
    • num_sem_ops:指定要执行的操作个数,即sem_ops数组中元素的个数。(semop对sem_ops数组中的每个成员按顺序执行,且该过程时原子操作)
  • 返回值:
    • 成功:0
    • 失败:-1(且sem_ops数组指定的所有操作不执行)

sembuf结构体:

struct sembuf{
    unsigned short int sem_num;/*信号量集中信号量的编号,从0开始*/
    short int sem_op;/*操作类型,可取正整数,0,负整数,分别代表对信号量不同的操作*/
    short int sem_flg;/*影响sem_op的可选值*/
}

sem_op和sem_flg的类型排列组合有些多且繁杂,本文不具体介绍,建议读者真正需要使用时再去了解即可。

semctl系统调用

#include<sys/sem.h>
int semctl(int sem_id, int sem_num, int command, ...);
  • 作用:对信号量进行直接的控制
  • 参数:
    • sem_id:由semget返回的信号量
    • sem_num:被操作的信号量在信号量集中的编号
    • command:指定信号量要执行的命令
    • ...:用户自定义参数,只有个别command需要写,取决于command的取值。
  • 返回值:
    • 成功:取决于command的取值
    • 失败:-1

command参数:

注意,command命令是不要求记忆的,包括我本身也不会去看,放在这里只是为了文章完整性以及查询的作用。这些参数都建议等具体使用的时候再进行查询


共享内存

共享内存是最高效的IPC机制,它不涉及进程间任何的数据传输。与之而来的缺点是,我们必须用其他辅助手段来同步进程对共享进程测访问,否则会产生竞态条件。

Linux中,共享内存API定义在sys/shm.h头文件中,包括4个系统调用:

shmget,shmat,shmdt和shmctl。

shmget系统调用

#include<sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
  • 作用:创建一段新的共享内存,或获取一段已经存在的共享内存
  • 参数:
    • key:键值,用来标识一段全局唯一的共享内存
    • size:指定共享内存的大小(如果是获取已经存在的共享内存,则设为0)
    • shmflg:指定一组标志来控制该API的细节
  • 返回值:
    • 成功:正整数值,是 共享内存的标识符
    • 失败:-1

同样,当shmget是创建一个共享内存时,与之关联的内核数据结构shmid_ds将被创建并初始化,后面的API的部分功能将修该结构体中的部分参数

struct shmid_ds
{
	struct ipc_perm shm_perm;/*共享内存的操作权限*/
    size_t shm_segsz;/*共享内存的大小,单位字节*/
    time_t shm_atime;/*对这段内存最后一次调用shmat的时间*/
    time_t shm_dtime;/*对这段内存最后一次调用shamd的时间*/
    time_t shm_ctime;/*对这段内存最后一次调用shmctl的时间*/
    pid_t shm_cpid;/*创建者的PID*/
    pid_t shm_lpid;/*最后一次执行shmat或shmdt操作的进程的PID*/
    shmatt_t shm_nattach;/*目前关联到此共享内存的进程数量*/
    /*省略一些字段*/
}
    

shmat和shmdt系统调用

#include<sys/shm.h>
void* shmat(int shm_id, const void* shm_addr, int shmflg);
  • 作用:将共享内存关联到地址空间
  • 参数:
    • shm_id:由shmget调用返回的共享内存标识符
    • shm_addr:指定将共享内存关联到进程的哪块地址空间
    • shmflg:影响最终的API的具体细节
  • 返回值:
    • 成功:返回共享内存被关联到的地址
    • 失败:返回 (void*) -1

int shmdt(const void* shm_addr);

作用:将关联到shm_addr的共享内存从进程中分离,成功返回0,失败返回 -1;

shmctl系统调用

#include<sys/shm.h>
int shmctl(int shm_id, int command, struct shmid_ds* buf);
  • 作用:控制共享内存的某些属性
  • 参数:
    • shm_id:由shmget返回共享内存标识符
    • command:要执行的命令
    • buf:取决于command
  • 返回值:
    • 成功:取决于command参数
    • 失败:-1

command命令:


消息队列

消息队列是两个进程之间传递二进制数据的一种有效的方式,每个数据块都有特定的类型,接收方可以根据类型来由选择地接收数据,而不一定像管道和命名管道那样必须以先进先出的方式接收数据。

Linux中,消息队列的API定义在sys/msg.h头文件中,包括四个系统调用 mssget,msgsnd,msgrcv,msgctl

msgget系统调用

#include<sys/msg.h>
int msgget(key_t key, int msgflg);
  • 作用:创建一个消息队列,或者获取一个已有的消息队列
  • 参数:
    • key:键值,标识一个全局唯一的消息队列
    • msgflg:同sem_flags,控制创建消息队列时的细节
  • 返回值:
    • 成功:返回一个正整数,代表消息队列的标识符
    • 失败:-1

同样,当msgget用于创建时,与之关联的内核数据结构msqid_ds将被创建并初始化:

struct msqid_ds {
	
   struct ipc_perm msg_perm;     /* 消息队列的操作权限*/
   time_t          msg_stime;    /* 最后一次调用msgsnd的时间*/
   time_t          msg_rtime;    /* 最后一次调用msgrcv的时间*/
   time_t          msg_ctime;    /* 最后一次被修改的时间*/
   unsigned long   __msg_cbytes; /* 消息队列中已有的字节数*/
   msgqnum_t       msg_qnum;     /* 消息队列中已有的消息数*/
   msglen_t        msg_qbytes;   /* 消息队列最大允许的字节数*/
   pid_t           msg_lspid;    /* 最后执行msgsnd的进程PID*/
   pid_t           msg_lrpid;    /* 最后执行msgrcv的进程PID*/
};

msgsnd系统调用

#include<sys/msg.h>
int msgsnd(int msqid, const void* msg_ptr, size_t msg_sz, int msgflg);
  • 作用:将一条消息加入消息队列
  • 参数:
    • msqid:由msgget调用返回的标识符
    • msg_ptr:指针,指向一个准备发送的消息(消息类型见下文)
    • msg_sz:消息的数据(mtxt)部分长度
    • msgflg:控制msgsnd的行为
  • 返回值:
    • 成功:0,并修改部分msqid_ds
    • 失败:-1
struct msgbuf{
    long mtype;/*消息类型*/
    char mtext[512];/*消息数据*/

msgrcv系统调用

#include<sys/msg.h>
int msgrcv(int msqid, void *msg_ptr, size_t msg_sz, long int msgtype,int msgflg);
  • 作用:从消息队列中获取消息
  • 参数:
    • msqid:由msgget调用返回的消息队列标识符
    • msg_ptr:用于存储接收的消息
    • msg_sz:消息数据部分的长度
    • msgtype:指定接收何种类型的消息
    • msgflg:控制msgrcv函数的行为
  • 返回值:
    • 成功:0,并修改msqid_ds的部分值
    • 失败:-1

msgctl系统调用

#include<sys/msg.h>
int msgctl(int msqid, int command, struct msqid_ds *buf);
  • 作用:控制消息队列的某些属性
  • 参数:
    • msqid:由msgget调用返回的消息队列标识符
    • command:要执行的命令
    • buf:
  • 返回值:
    • 成功:取决于command参数
    • 失败:-1

command参数:

一些废话

写完这篇文章时已经是2024年的2月1日,距离这个专栏开始(2024年1月12日)已经过去了差不多三周。说实话我并不满意这个速度,在这20天里,我真正的学习的时间只有14天而已。

我每天会带上电脑,早上九点到区图书馆的自习室学习,上午写算法,下午就看书学习写专栏和博客。最初我的设想是晚上九点图书馆闭馆再回家,但往往下午六点吃完晚饭就回去了。我看过同校的一个大佬的学习经历,他在大一的暑期时就已经天天泡市图书馆了。这也是为什么别人早早进大厂,而我却找不到工作的原因。

但我确实是无法一天十二个小时都在学习,我晚上不回去打一会游戏,没多久我就坚持不下去了。包括过去的二十天,因为幻兽帕鲁的开服,我建了个服务器天天晚上都和室友在玩,每天晚上玩两三个小时的游戏是我生活的聊聊慰藉。

除了和朋友玩,我每天睡觉前还要和异地的对象玩金铲铲之战。她本来是完全不玩游戏的,我强行拉她入坑,现在她每天晚上拉我玩,不然我真不知道异地的这些日子怎么维系感情,我白天忙学习,晚上玩游戏,本就没时间和她聊天,要是没有金铲铲,估计感情用不了多久就GG了。(感谢金铲铲)

回到正题,我现在的学习规划是在这段时间同步把这个Webserver的项目和Carl的代码随想录做完。在学有余力的情况下,学习Linux的常用操作命令(应该会再开一个专栏)。

下一个阶段的学习计划是开始看哈工大的 OS网课,保持算法手感的同时开始做OS的项目(具体做MIT6.S081还是操作系统真象还原还不确定)。

等两个项目都完成后,开始大量背八股,投简历,投实习,希望能在4月前找到个实习吧。

end

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

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

相关文章

AI与数字孪生

源自&#xff1a;译文 “人工智能技术与咨询”发布 AI和数字孪生 预测分析&#xff1a;网络安全水晶球 面对不断变化的威胁&#xff0c;提供自适应安全防护 自然语言处理&#xff1a;解码威胁语言 先进技术&#xff1a;人工智能作为数字孪生安全的基础 道德考量 …

关于v8垃圾回收机制以及与其相关联的知识点--还没整理版本

对于值类型b来说&#xff0c;就直接释放了其占用的内存&#xff0c;对于引用类型obj来说&#xff0c;销毁的只是变量obj对堆内存地址 1001 的引用&#xff0c;obj的值 { c: 3 } 依然存在于堆内存中。那么堆内存中的变量如何进行回收呢&#xff1f; V8的垃圾回收策略主要是基于…

git将项目的某次签入遴选(Cherry-Pick)另一个项目

需求&#xff1a;将项目Product&#xff0c;分支feature/platform&#xff0c;签入959294ce6b75ee48c5cb22c46d7398654628a896&#xff0c;遴选到项目BRP&#xff0c;分支dev 第一步&#xff1a;使用原签入生成patch文件&#xff08;git format-patch -1 <commit_hash>&a…

西部数据DX4000数据恢复案例

西部数据DX4000是一款NAS&#xff08;网络附加存储&#xff09;产品&#xff0c;它集成了存储、备份、共享和远程访问企业数据等多种功能。然而&#xff0c;和任何电子设备存在一样通病&#xff0c;DX4000也可能会出现故障&#xff0c;导致数据无法访问。下面是一个客户的西部数…

Python OpenCV实现图片像素区域缩放

Python OpenCV实现图片像素区域缩放 前言项目安装OpenCV和Pillow思路代码编写 前言 遇到一个要将大量图片缩放成统一规格的难题&#xff0c;并且这些图片周围还有很多空白像素&#xff0c;所以用Python实现一下。 项目 安装OpenCV和Pillow pip install opencv-python pip …

基于YOLOv8的水下生物检测,多种优化方法---DCNv4结合SPPF,效果秒杀DCNv3,涨点两个点(四)

&#x1f4a1;&#x1f4a1;&#x1f4a1;本文主要内容:详细介绍了水下生物检测整个过程&#xff0c;从数据集到训练模型到结果可视化分析&#xff0c;以及如何优化提升检测性能。 &#x1f4a1;&#x1f4a1;&#x1f4a1;加入DCNv4_SPPF mAP0.5由原始的0.522提升至0.543 1.水…

7.3、向量空间的简要回顾

7.3、向量空间的简要回顾 ​ 在开始讨论格之前&#xff0c;我们先提醒读者注意线性代数中的一些重要定义和思想。向量空间的定义可以非常宽泛&#xff0c;但就本章而言&#xff0c;我们只需考虑对于某个正整数 m&#xff0c;包含在 R m R^{m} Rm中的向量空间即可。 ​ 我们从…

[职场] 阐述演员这个职业 #微信#学习方法#媒体

阐述演员这个职业 演员这个职业是一种极具挑战性和魅力的职业&#xff0c;它让人们有机会通过表演来展现自己&#xff0c;将自身的才华和潜力发挥到极致。 1. 演员需要具备扎实的专业素养&#xff0c;包括表演技巧、语言技巧、心理技巧等。演员需要深入理解角色&#xff0c;从…

大数据 - Spark系列《一》- 分区 partition数目设置详解

目录 &#x1f436;3.2.1 分区过程 &#x1f436;3.2.2 SplitSize计算和分区个数计算 &#x1f436;3.2.3 Partition的数目设置 1. &#x1f959;对于数据读入阶段&#xff0c;输入文件被划分为多少个InputSplit就会需要多少初始task. 2. &#x1f959;对于转换算子产生的…

【Bugs】Jmeter报错:NoSuchMethodError: org.apache.jmeter.samplers.

报错情况 Jmeter版本&#xff1a;5.4.3 报错场景&#xff1a;在线程组中添加了jpgc - PerfMon Metrics Collector性能监控组件后出现报错。 Jmeter中无法运行测试&#xff0c;cmd命令行中出现以下报错。 cmd报错详细内容&#xff1a; Uncaught Exception java.lang.NoSuchMe…

如何系统的自学Python?通义千问、讯飞星火、文心一言及ChatGPT的回答

如何系统的自学Python&#xff1f;来看看通义千问、讯飞星火、文心一言及ChatGPT的回答. 第一个是马老师的通义千问 系统地自学Python是一个循序渐进的过程&#xff0c;从基础语法到实践项目&#xff0c;再到专业领域的深入学习。下面是一个详细的步骤指南&#xff1a; 了解Py…

Qt5 基于OpenGL实现六轴机械臂三维仿真

需求 在Qt中通过OPenGL方式加载三维模型STL文件&#xff0c;然后将多个结构的STL文件类型的模型进行组装&#xff0c;形成6轴机械臂三维模型的显示&#xff0c;并且可以对每个关节进行关节角度的控制。 新建一个C类STLFileLoader&#xff0c;用于加载STL文件&#xff0c;并进…

电口模块的应用:实现高速网络通信

随着互联网的普及和信息时代的到来&#xff0c;网络通信已经成为了现代社会中不可或缺的一部分。电口模块作为网络通信中的重要组成部分&#xff0c;其应用也越来越广泛。在本文中&#xff0c;我们将详细介绍电口模块的优势以及应用领域。 首先&#xff0c;电口模块具有高速传…

JavaScript 与Java什么关系?为什么名字中带有Java?

JavaScript与Java关系 JavaScript和Java是两种不同的编程语言&#xff0c;它们之间没有直接关系。尽管它们都以“Java”命名&#xff0c;但是它们的语法、用途和应用场景都不同。 Java是一种面向对象的、静态类型的编程语言&#xff0c;主要用于开发独立应用程序、网络应用、…

unity 拖入文件 窗口大小

目录 unity 拖入文件插件 设置窗口大小 unity 拖入文件插件 GitHub - Bunny83/UnityWindowsFileDrag-Drop: Adds file drag and drop support for Unity standalong builds on windows. 设置窗口大小 file build

golang Cannot assign a value to the unexported field ‘xxxxx‘

最近学习golang&#xff0c;结果发现参考github的代码报错了 查了一下资料&#xff0c;这里记录加吐槽一下&#xff0c;这个设定真的让我感觉痛苦 go 实例化结构体报错 Cannot assign a value to the unexported field xxxxx 或者是报错implicit assignment of unexported fiel…

计算机系统体系结构

文章目录 计算机系统体系结构1. 什么是计算机体系结构术语解释计算机系统体系结构所涉及的内容简单通用计算机结构计算机指令程序执行过程时钟 2. 计算机的发展机械计算机机电式计算机早期电子计算机微机和PC革命移动计算和云计算摩尔定律乱序执行 3. 存储程序计算机寄存器传输…

qt学习:停车场管理系统+摄像头+http识别车牌+sqlite3数据库

目录 参考前面发的几篇文章http识别车牌&#xff0c;sqlite3数据库、摄像头的文章 步骤 部分代码 新建一个项目&#xff0c;加入前面用到的http和image两个文件&#xff0c;和加入用到的模块和头函数和成员&#xff0c;加入前面用到的全局变量 配置ui界面 在构造函数中初…

sql指南之null值用法

注明&#xff1a;参考文章&#xff1a; SQL避坑指南之NULL值知多少&#xff1f;_select null as-CSDN博客文章浏览阅读2.9k次&#xff0c;点赞7次&#xff0c;收藏21次。0 引言 SQL NULL&#xff08;UNKNOW&#xff09;是用来代表缺失值的术语&#xff0c;在表中的NULL值是显示…

微服务入门篇:Ribbon负载均衡(原理,均衡策略,饥饿加载)

目录 1.负载均衡原理2.负载均衡策略3.饥饿加载 1.负载均衡原理 在使用 LoadBalanced 注解后&#xff0c;Spring Cloud Ribbon 将会为 RestTemplate 添加负载均衡的能力。 负载均衡的流程如下&#xff1a; 当使用 RestTemplate 发送请求时&#xff0c;会先判断请求的 URL 是否包…