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

前言

        在学习操作系统时,我们知道现代计算机往往都是多进程多线程的,多进程和多线程技术能大大提高了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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

养猫家庭必备猫用空气净化器哪款牌子好?宠物空气净化器值得推荐的品牌

养宠家庭的朋友们都知道&#xff0c;猫咪的浮毛无处不在&#xff0c;每天都会在空气中飘荡。无论是沙发、地板还是衣服&#xff0c;都成了浮毛的重灾区。这些浮毛不仅难以清理&#xff0c;而且对于呼吸道敏感的人来说&#xff0c;可能会引发过敏反应。为了除去猫毛&#xff0c;…

Zoho Mail 2023:回顾过去,展望未来:不断进化的企业级邮箱解决方案

当我们告别又一个非凡的一年时&#xff0c;我们想回顾一下Zoho Mail如何融合传统与创新。我们迎来了成立15周年&#xff0c;这是一个由客户、合作伙伴和我们的敬业团队共同庆祝的里程碑。与我们一起回顾这段旅程&#xff0c;探索定义Zoho Mail历史篇章的敏捷性、精确性和创新性…

2024美赛预测算法 | 回归预测 | Matlab基于WOA-LSSVM鲸鱼算法优化最小二乘支持向量机的数据多输入单输出回归预测

2024美赛预测算法 | 回归预测 | Matlab基于WOA-LSSVM鲸鱼算法优化最小二乘支持向量机的数据多输入单输出回归预测 目录 2024美赛预测算法 | 回归预测 | Matlab基于WOA-LSSVM鲸鱼算法优化最小二乘支持向量机的数据多输入单输出回归预测预测效果基本介绍程序设计参考资料 预测效果…

vit细粒度图像分类(八)SIM-Trans学习笔记

1.摘要 细粒度视觉分类(FGVC)旨在从相似的从属类别中识别物体&#xff0c;这对人类准确的自动识别需求具有挑战性和实用性。大多数FGVC方法侧重于判别区域挖掘的注意机制研究&#xff0c;而忽略了它们之间的相互依赖关系和组成的整体对象结构&#xff0c;而这些对模型的判别信…

防御保护---防火墙双机热备直路部署(上下三层接口)

防御保护---防火墙双机热备直路部署&#xff08;上下三层接口&#xff09; 一、根据网段划分配置IP地址和安全区域二、配置动态路由OSPF三、配置双机热备四、测试&#xff1a;4.1 测试一&#xff1a;查看状态和路由器路由表&#xff08;双机热备&#xff09;前后对比4.2 测试二…

2024年美赛数学建模B题思路分析 - 搜索潜水器

# 1 赛题 问题B&#xff1a;搜索潜水器 总部位于希腊的小型海上巡航潜艇&#xff08;MCMS&#xff09;公司&#xff0c;制造能够将人类运送到海洋最深处的潜水器。潜水器被移动到该位置&#xff0c;并不受主船的束缚。MCMS现在希望用他们的潜水器带游客在爱奥尼亚海底探险&…

2024年美赛美国大学生数学建模竞赛BCEF题思路解析+代码+论文

下文包含&#xff1a;2024年美国大学生数学建模竞赛&#xff08;美赛&#xff09;A- F题思路解析、选题建议、代码可视化及如何准备数学建模竞赛&#xff08;2号发&#xff09; 将会第一时间发布选题建议、所有题目的思路解析、相关代码、参考文献、参考论文等多项资料&#x…

配网故障预警定位装置_故障预警_故障定位_深圳恒峰

随着社会经济的快速发展&#xff0c;电力需求不断增长&#xff0c;电力系统的安全稳定运行对于国家经济发展和民生改善具有重要意义。然而&#xff0c;电力系统的复杂性和不确定性使得设备故障、线路跳闸等问题时有发生&#xff0c;给电力系统的正常运行带来极大隐患。为了解决…

前后端分离,RSA加密传输方案

1.原理 RSA是一种非对称加密算法。通过生成密钥对&#xff0c;用公钥加密&#xff0c;用私钥解密。对于前后端分离的项目&#xff0c;让前端获取到公钥对敏感数据加密&#xff0c;发送到后端&#xff0c;后端用私钥对加密后的数据进行解密即可。 2.实现 RSA工具类&#xff1…

ffmpeg合成mp3音频,解决音频属性不一致问题

1. 需求&#xff0c;amr转成mp3&#xff0c;再将此mp3和其他mp3合成 2. 问题&#xff1a;拼接后的第一段音频可以播放&#xff0c;第二段自动跳过&#xff0c;无法播放。 3. 解决&#xff1a; 3.1 查看各文件属性 # 查看amr转为mp3文件的属性&#xff1a;ffprobe 文件名&am…

网络空间测绘在安全领域的应用(上)

近年来&#xff0c;网络空间测绘已经跻身为网络通信技术、网络空间安全、地理学等多学科融合的前沿领域。 该领域聚焦于构建网络空间信息的“全息地图”&#xff0c;致力于建立面向全球网络的实时观测、准确采样、映射和预测的强大基础设施。 通过采用网络探测、数据采集、信…

少儿编程 中国电子学会图形化编程2022年6月等级考试Scratch三级真题解析(选择题、判断题)

1.点击绿旗&#xff0c;舞台上的角色会说出&#xff1f; A&#xff1a;2022年5月1日 B&#xff1a;1日5月2022年 C&#xff1a;2022年05月01日 D&#xff1a;05月01日2022年 2.观察规律&#xff0c;请问橙色方块应填写的数字是&#xff1f; A&#xff1a;4 B&#xff1a;5…

spring-data-redis自定义实现看门狗机制

文章目录 前言redission分布式锁看门狗机制简单流程图 spring-data-redis实现看门狗机制指南开始引入依赖配置redis连接以及基础配置实现redis分布式锁工具类直接失败和锁重试机制实现 效果图展示 前言 项目中使用redis分布式锁解决了点赞和楼层排序得问题&#xff0c;所以这里…

Java的Mysql使用

Java的Mysql使用 说明 通过Java的方式连接Mysql中的数据库&#xff0c;并对数据库中的数据进行增加 查询操作 ​ 使用Mysql所提供的第三方库中的类(Mysql的API) 对其进行操作 ​ 将Mysql Jar包添加到lib目录后&#xff0c;就可以使用其中的类对其Mysql数据库进行操作 Mysq…

语义分割:从早期探索到深度学习的突破

语义分割&#xff1a;从早期探索到深度学习的突破 语义分割的端倪&#xff1a;从早期探索到深度学习的突破引言早期技术&#xff1a;图像处理与模式识别边缘检测区域生长图割(Graph Cut)聚类方法 深度学习的兴起&#xff1a;CNN革命2012年 AlexNet的突破全卷积网络&#xff08;…

网络编程之ip地址

目录 1、概念&#xff1a; 2、所属的tcp/ip协议的层 3、IP地址查询 &#xff08;1&#xff09;Linux &#xff08;2&#xff09;windows 4、IP地址结构组成及分类 &#xff08;1&#xff09;组成&#xff08;网络位主机位&#xff09; &#xff08;2&#xff09;分类 1、…

文心一言 VS 讯飞星火 VS chatgpt (192)-- 算法导论14.2 2题

二、用go语言&#xff0c;能否在不影响红黑树任何操作的渐近性能的前提下&#xff0c;将结点的黑高作为树中结点的一个属性来维护&#xff1f;说明如何做&#xff0c;如果不能&#xff0c;请说明理由。如何维护结点的深度&#xff1f; 文心一言&#xff1a; 在Go语言中&#…

Oracle闪回日志管理(flashbackup log)

Oracle闪回日志管理&#xff08;flashbackup log&#xff09; 1.开启闪回日志 shutdown immediate startup mount; alter database archivelog; alter database flashback on; alter database open; 2、查看闪回日志保留期限 show parameter flash 默认是1440分钟&#xff0c…

宋仕强论道之华强北蓝牙耳机(三十七)

华强北最近几年最火爆的产品就是TWS蓝牙耳机了&#xff0c;蓝牙耳机是指通过蓝牙无线技术连接智能手机等设备&#xff0c;实现无线通讯的一种设备。因为苹果蓝牙应用技术的突破&#xff0c;而呈现爆发之势&#xff0c;目前蓝牙耳机的市场存量是非常之大&#xff0c;全世界智能手…

gitlab ci cd 不完全指南

gitlab 可能大家很常用&#xff0c;CI、CD 也应该早有耳闻&#xff0c;但是可能还没有去真正地了解过&#xff0c;这篇文章就是我对 gitlab CI、CD 的一些理解&#xff0c;以及踩过的一些坑&#xff0c;希望能帮助到大家。 什么是 CI、CD CI&#xff08;Continuous Integrati…