【Linux】进程等待 | 详解 wait/waitpid 的 status 参数
🤣 爆笑教程 👉 《看表情包学Linux》👈 猛戳订阅 🔥
💭 写在前面:在上一章中我们讲解了进程创建与进程终止,本章我们开始讲解进程等待。进程等待这部分知识相较于前面还是较为复杂的,我会由浅入深地讲解这部分的知识点,值得一提的是在学习本章前需要掌握进程状态的知识,复习链接我会贴到下面。我们先谈论进程等待的必要性,简单地讲解 wait 函数,然后我们主要讲解 waitpid 函数。由于 wait 只有一个参数 status,且 waitpid 有三个参数且其中一个也是 status,我们本章重点讲解这个 status 参数。因为要控制篇幅,本章没办法讲清楚 waitpid 的其余参数(比如 options 参数),我们将放到后期讲解,所以算是对 waitpid 函数的一个初探,后面我们还会继续讲解它的。
🔗 前置知识:【看表情包学Linux】进程状态
Ⅰ. 进程等待(Process wait)
0x00 引入:进程等待的必要性
❓ 为什么要进行进程等待?不知道大家是否还记得我们在之前的章节讲过的 "僵尸进程" 的概念。
僵尸状态:当一个
中的进程退出的时候,一般不会直接进入
状态(死亡,资源可以立马被回收),而是进入
状态。
子进程退出如果父进程不管不顾,就 可能造成僵尸进程的问题,一直占内存进而引发内存泄露。
"这是典型的占着茅坑不拉翔行为"
此外,进程一旦变成僵尸状态,就会变得刀枪不入,不可被杀,因为我们说过:
甚至 "杀进程不眨眼的" kill -9
也无法奈其何!
所以我们必须让其从 状态变为
状态:
进而允许操作系统能去释放它(将代码和数据 free 掉,将相关数据结构归还给 slab
分派器)。
上面我们讲的实际上就是我们需要进程等待的一个原因 —— 解决内存泄露问题 。
然而不仅仅这一个原因,我们还需要进程等待来 获取子进程的退出状态 。
我们需要知道父进程派给子进程的任务完成的如何。
比如子进程运行完成,结果对还是不对,是否正常退出?
通过进程等待的方式,回收子进程资源,获得子进程退出信息。
(当然了,并不是所有的父进程需要关心子进程的。所以是存在父进程不等待子进程的情况的,处理的方案我们在之前也有所铺垫,该部分话题我们会在后期讲信号的时候进一步讲解。本章我们只关心父进程必须等待子进程的情况,解决僵尸进程问题让子进程尽快恢复,获取子进程的退出状态)
获取子进程的退出状态是否需要将曾经子进程的退出信息保存起来,然后被恢复、读取呢?
这和我们刚才讲的进程退出有着大大的关系!我们知道了进程退出是有退出码的。
我们需要让子进程退出时它的 return
结果或者 exit 的结果是需要被父进程读到的。
🔺 总结:需要进程等待的原因:① 解决内存泄露问题 ② 获取子进程的退出状态
0x01 wait 函数
说得好,那么如何等待呢?
我们先来介绍一下 wait 函数。第一种方式,就是让父进程调用 wait 即可。
wait() 可以解决回收子进程 状态,让子进程进入
状态。
#include <sys/types.h>
#include <sys/wait.h>pid_t wait(int* status);
我们看到,wait 有一个叫 status 的参数(我们暂时不说,下面讲 waitpid 后会详细讲解)
我们下面验证一下这个函数是否可以 ,我们会把 status 暂且设置为空。
💬 验证:调用 函数回让父进程 "礼貌地" 等待子进程
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>int main (void)
{pid_t id = fork();if (id == 0) {// childwhile (1) {printf("我是子进程,我正在运行... Pid: %d\n", getpid());sleep(1);}}else {printf("我是父进程: pid: %d,我将耐心地等待子进程!\n", getpid());sleep(20); // 为了便于观察,我们让父进程休眠20s// 苏醒后,父进程执行 wait,耐心地等待子进程pid_t ret = wait(NULL); // 暂且将status参数设置为NULLif (ret < 0) {printf("等待失败!\n");}else {printf("等待成功!\n"); // 此时 Z → X}sleep(20); // 子进程退出后,再让父进程存在一段时间}
}
💡 说明:简单来说,就是让父子进程都跑起来,并各自打印消息,子进程一直死循环运行,父进程将自己休眠 20 秒,在这 20 秒之内我们手动把处于死循环的子进程给 kill 掉,此时子进程会处于 状态,此时父进程还在休眠不会立即调用 wait(这么做是为了便于观察)。
当 20 秒过后,父进程苏醒后执行了 wait 函数,用一个变量 ret 去接收 wait 的返回值,通过返回值来确定是否等待成功。如果等待成功了就会成功 。
为了观察进程状态,我们写一段监控脚本,做到每隔一秒就监控一下我们的 mytest 进程。
🔍 监控脚本:
$ while :; do ps ajx | head -1 && ps ajx | grep mytest | grep -v grep; echo "-----------------------------------------------------------------------"; sleep 1; done
运行后我们只需要死死盯着
,观察状态的变化即可!
开三个窗口,分别用于运行监控脚本、运行 mytest 和 kill 子进程:
🚩 开始运行: ./mytest
至此,我们成功验证了父进程等待了子进程。
通过 wait() 的方案,我们可以解决回收子进程的 状态,让子进程进入
状态。
0x02 waitpid 函数初探
刚才讲的 wait 并不是主角,因为其功能比较简单,在进程等待时用的更多的是 waitpid
waitpid 可以把 wait 完全包含,wait 是 waitpid 的一个子功能。
📌 注意:wait/waitpid 都可以回收子进程的僵尸状态()。
waitpid:等待任意一个退出的子进程。
#include <sys/types.h>
#include <sys/wait.h>pid_t waitpid(pid_t pid, int* status, int options);
对于返回值 pid_t:
- 如果 pid_t > 0:等待子进程成功,返回值就是子进程的 pid;
- 如果 pid_t < 0:等待失败。
对于参数 pid:
- 设置参数 pid > 0:是几,就代表等待哪一个子进程,比如 pid=1234,指定等待。
- 设置参数 pid = -1:待任意进程。
对于参数 options:
- 0:阻塞等待 (本章不讲)
对于参数 status:
- 该参数是一个输出型参数,通过调用该函数,从函数内部拿出特定的数据。
(本章我们将重点讲解参数 status)
值得强调的是,wait/waitpid 是系统调用!
💡 说明:父进程等待子进程,子进程也会执行自己的代码。当子进程执行了 return/exit 退出后,子进程会将自己的退出码信息写入自己的进程控制块 () 中。子进程退出了,代码可以释放,子进程退出后变成
状态,其本质上就是将自己的 task_struct 维护起来(代码可以释放,但是 task_struct 必须维护)。所谓的 wait/waitpid 的退出信息,实际上就是从子进程的 task_struct 中拿出来的,即 从子进程的 task_struct 中拿出子进程退出的退出码。
所以,我们的父进程在等待子进程死亡,等子进程一死,就直接把子进程的退出码信息拷贝过去,通过 wait/waitpid 传进来的参数后,父进程就拿到了子进程的退出结果。即 子进程会将自己的退出信息写入 task_struct 。
子进程的 task_struct 是操作系统的内部数据结构这个 waitpid 自然就是系统调用,实际上就是调用操作系统的功能。
那……这个 status 仅仅只是一个无情的拿退出码的机器吗?
当然不是!肯定还会包含其他信息的,这个我们一会讲解。
我们再来验证一下 "子进程会将自己的退出信息写入 task_struct" 这句话:
💡 解读:这里我们可以看到 exit_state,这就是退出状态了,下面的 exit_code 和 exit_signal 分别是退出码和退出信号,当一个进程异常退出时该进程会收到信号,所以收到的信号编号也记录在了这里。这两个信息会在子进程任何地方退出时填充,被填充后,父进程就可以读取 task_struct 中的这两个数据,来得到子进程的退出结果。
🔺 总结:父进程通过调用 wait/waitpid,通过 status 参数,就可以将子进程的信息拿到手。
由此可见,这个 status 是多么的重要!我们现在就来重点来讲解一下 status!
Ⅱ. 详解 wait/waitpid 的 status 参数
0x00 引入:status 参数是位图结构
wait 和 waitpid,都有这个 status 参数,我们刚才演示 wait 的时候传的是 NULL,
如果传递 NULL,则表示不关心子进程的退出状态信息。否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
该参数是一个 输出型参数 (即通过调用该函数,从函数内部拿出来特定的数据)。
并且,status 参数是由操作系统填充的!是一个整数,该整数就是下面我们要详细研究的。
它虽然是一个 int 型整数,但是不能简单地将其看作整型,而是被当作一个 位图结构 看待。
不过,关于 status 我们只需要关心该整数的 低 16 个比特位!
我们不必去关心它的高 16 位,因为凭借低 16 位就足以判断了。
然而,整数的低 16 位,其中又可以分为 最低八位 和 次低八位(具体细节看图):
那么我们你可以通过它们来干什么呢?我们先来讲解低十六位中的 "次低八位" 。
0x01 次低八位:拿子进程退出码
📌 重点:通过提取 status 的次低八位,就可以拿到子进程的退出码。
💬 验证: 通过提取 status 的次低八位,可以拿到子进程的退出码。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>int main (void)
{pid_t id = fork();if (id == 0) {int cnt = 5; // 循环5次// childwhile (1) {// 五秒之内运行状态printf("我是子进程,我正在运行... Pid: %d\n", getpid());sleep(1);// 五秒之后子进程终止cnt--;if (cnt == 0) {break; }}exit(233); // 方便辨识,退出码我们设置为233,这是我们的预期结果}else {printf("我是父进程: pid: %d,我将耐心地等待子进程!\n", getpid());// ***** 使用waitpid进行进程等待int status = 0; // 接收 waitpid 的 status 参数pid_t ret = waitpid(id, &status, 0);if (ret > 0) { // 等待成功printf ("等待成功,ret: %d, 我所等待的子进程退出码: %d\n", ret,(status>>8)&0xFF);}}
}
💡 说明:我们让 child 子进程运行五秒后自动结束,之后通过 exit 返回,为了方便辨识,我们可以将退出码设置的花一点,这里我设置了个 233。父进程调用 waitpid 函数,并用 ret 接收,如果返回值大于 0 则代表等待成功,此时我们打印出子进程的退出码,因为 "通过提取 status 的次低八位,就可以拿到子进程的退出码。" ,所以我们可以通过 status 参数去拿。
我们说了,status 并不是整体使用的,而是区域性使用的,我们要取其次低八位。我们可以用 位操作 来完成,将 status 右移八位再按位与上 ,即 (status>>8)&0xFF ,就可以提取到 status 的次低八位了。
如果不出意外的话,运行后 waitpid 等待成功,子进程的返回值应当是 233,就证明了可以通过提取 status 的次低八位拿到子进程的退出码。
🚩 运行结果如下:
0x02 初识 core dump(核心转储)
(由于还没有讲解信号的部分的知识,我们先做一个简单的了解)
我们看到,core dump 是占了 1 个 比特位的:
core dump 指的是 核心转储,也可以称之为 "吐核"。
它是操作系统在进程收到某些信号而终止运行时,将此时进程地址空间的内容以及有关进程状态的其他信息写出的一个磁盘文件。目前只需要知道,该信息是用于调试的。
0x03 最低七位:提取子进程的退出信号
📌 重点:通过提取 status 的最低七位,就可以拿到子进程的退出信号。
我们的 status 的低八位用于表示处理异常的地方,其中有 1 位是 core dump,我们下面讲。
除去 core dump,剩余七位用于进程中的退出信号,这就是 最低七位。
进程退出,如果异常退出,是因为这个进程收到了特定的信号。
我们虽然还没有开始讲解信号,但是我们前几张就介绍了 kill -9 这样的杀进程操作。
这个 -9 我们当时说了,就是一个信号,发送该信号也确实可以终止进程。
刚才我们讲的 wait/waitpid 和次低八位的时侯,都是关于进程的 正常退出。
如果进程 异常退出 呢?我们来模拟一下进程的异常退出。
💬 代码演示:模拟异常退出的情况,让子进程一直跑,父进程一直等。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>int main (void)
{pid_t id = fork();if (id == 0) {// 子进程一直不退出,父进程会一直等待。// childwhile (1) {printf("我是子进程,我正在运行... Pid: %d\n", getpid());sleep(1);}exit(13);}else {printf("我是父进程: pid: %d,我将耐心地等待子进程!\n", getpid());int status = 0;pid_t ret = waitpid(id, &status, 0);if (ret > 0) { // 等待成功printf("等待成功,ret: %d, 我所等待的子进程退出码: %d\n, 退出信号是: %d", ret, (status>>8)&0xFF, status&0x7F);}}
}
💡 说明:现在我们直接 while(1) 死循环让子进程往死里跑,此时父进程由于调用了 waitpid,就会一直等待子进程,父进程就会持续阻塞。
此时我们讲父进程打印的消息再增添一个 "退出信号",也就是提取 status 的最低七位,我们想让低七位保留,高二十五位保留,让 status 按位与上 即可,即 status&0x7F (低七位保留,高二十五位全部清零) 。如此一来,我们就提取到了子进程退出时的退出信号了。
🚩 运行结果如下:
因为子进程是个死循环,父进程又调了 waitpid,导致父进程一直在 "阻塞式" 地等待子进程。
父进程在等待子进程期间什么都没有干,就搬了张板凳坐在那等子进程死。
父进程就像在一直在问子进程:
"你寄没寄?你寄没寄?你寄没寄?"
我们知道,信号是可以杀掉进程的,我们现在主动输入 kill -9:
此时我们就成功拿到了子进程的退出信号,9 是因为我们输入的信号就是 9。
此时父进程看到子进程寄了,终于可以不用等了,可以给子进程收尸了()
父进程大喊道:
"你终于寄了!寄你太美!oh baby,寄你太美!寄你实在太美!"
还是那句话,代码跑完结果是什么已经不重要了,我们最关心的是因为什么原因退出的。
当进程收到信号时,就代表进程异常了。进程程出,如果是异常退出,是因为该进程收到了特定的信号。其实除了 9 号信号还有很多信号,输入 kill -l 就可以查看这些:
比如我们遇到的除零警告,即 warning: division by zero
int a = 10 / 0;
这时出现 8 号退出信号:
(Float Pointer Error)
实际上,我们在编程学习中遇到的程序崩溃,崩溃程序就退出了,程序一崩溃程序就退出了,今天在我们操作系统的角度来看,这就是进程终止。其实是因为错误而导致了软硬件错误,操作系统通过发送信号的方式把进程杀掉了。至于操作系统凭什么可以终止, 又是怎么发信号的?又是如何终止的?详细的过程是什么?这些问题我们会放到后面的信号章节再做详细讲解!
🔺 总结:退出信号代表进程是否异常,退出码代表进程在退出之时代码对还是不对。
0x04 进程退出的宏
我们今天写的代码,是通过位操作去截 status 得到退出码和退出信号的。
实际上,你也可以不用位操作,因为 已经给我们提供了一些宏供我们直接调用。
它们是 WEXITSTATUS 和 WIFEEXITED,在这之前,我们再思考一个问题:
❓ 思考:一个进程退出时,可以拿到退出码和推出信号,我们先看谁?
一旦程序发现异常,我们只关心退出信号,退出码没有任何意义。
所以,我们先关注退出信号,如果有异常了我们再去关注退出码。
WEXITSTATUS 宏用于查看进程的退出码,若非 0,提取子进程退出码。
WEXITSTATUS(status)
WIFEEXITED 宏用于查看进程是否正常退出,如果是正常终止的子进程返回状态,则为真。
WIFEXITED(status)
所以,我们可以通过宏来获取退出码和退出信号,是不是很方便?
💬 代码演示:运用 WEXITSTATUS 和 WIFEXITED 宏
else {printf("我是父进程: pid: %d,我将耐心地等待子进程!\n", getpid());int status = 0;pid_t ret = waitpid(id, &status, 0);if (ret > 0) { // 等待成功if (WIFEEXITED(status)) {printf("子进程正常退出,退出码: %d\n", WEXITSTATUS(status));}}}
当然了,如果你压根就不关注推出信息和退出码,你直接把 status 设置为 NULL 就行。
options 参数
默认为 0,表示阻塞等待。
当我们调用某些函数的时侯,因为条件不就绪,需要我们阻塞等待。
本质:就是当前进程自己变成阻塞状态,等条件就绪的时候,再被唤醒。
所谓的阻塞,本质就是进程阻塞。所谓的条就不就绪,可能是任意的软硬件条件。
还有一个非阻塞等待的知识点,我们放到后面再说吧。
📌 [ 笔者 ] 王亦优
📃 [ 更新 ] 2022.3.4
❌ [ 勘误 ] /* 暂无 */
📜 [ 声明 ] 由于作者水平有限,本文有错误和不准确之处在所难免,本人也很想知道这些错误,恳望读者批评指正!
📜 参考资料 C++reference[EB/OL]. []. http://www.cplusplus.com/reference/. Microsoft. MSDN(Microsoft Developer Network)[EB/OL]. []. . 百度百科[EB/OL]. []. https://baike.baidu.com/. 比特科技. Linux[EB/OL]. 2021[2021.8.31 xi |
相关文章:

【Linux】进程等待 | 详解 wait/waitpid 的 status 参数
🤣 爆笑教程 👉 《看表情包学Linux》👈 猛戳订阅 🔥 💭 写在前面:在上一章中我们讲解了进程创建与进程终止,本章我们开始讲解进程等待。进程等待这部分知识相较于前面还是较为复杂的࿰…...

OpenAI眼中的无线调优策略
问:无线调优策略该怎么优化无线调优是指对无线网络的各种参数进行优化,以提高网络性能和用户体验。以下是几个无线调优策略:频谱分配:通过优化频谱的分配,可以提高网络的容量和覆盖范围。在频谱分配时,需要…...

DataX入门
目录 1. DataX介绍 2. DataX支持的常用数据源类型 3. 设计理念 4. DataX框架设计 4.1. Reader 4.2. Writer 4.3. Framework 5. DataX的运行流程 6. DataX与Sqoop对比 7. 部署 8. 配置详解 9. 案例 同步MySql到HDFS 9.1. 整体结构 9.2. mySqlReader 9.2.1. …...

第二章SpringBoot基础学习
文章目录SpringBoot依赖管理特性依赖管理开发导入starter场景启动器SpringBoot自动配置特性自动配好Tomcat自动配好SpringMVC默认的包结构各种配置拥有默认值按需加载所有自动配置项SpringBoot注解底层注解ConfigurationImport导入组件Conditional条件装配ImportResource导入Sp…...

B - Build Roads (最小生成树 + 打表)
https://vjudge.net/problem/Gym-103118B/origin 在猫的国度里,有n个城市。猫国国王想要修n -1条路来连接所有的城市。第i市有一家ai经验价值的建筑公司。要在第i市和第j市之间修建公路,两个城市的建筑公司需要相互合作。但是,在修路的过程中…...

k8s管理工具
k8s管理工具 文章目录k8s管理工具Kuboard(💕17.3k)KubeOperator(💕4.6k)Rainbond(💕3.8k)KubeSphere(💕12k)Kuboard(&…...

Cannot start compiler The output path is not specified for module mystatic(已解决)
1.背景:今天在idea上写了一些代码,右键run竟然跑不起来了,而且右下角的Event Log还报错。报错内容如下图:2.报错原因:项目代码和编译器的输出路径不在一块,导致idea无法找到模块的output path(输…...

python入门应该怎么学习
国外Python的使用率非常高,但在国内Python是近几年才火起来,Python正处于高速上升期市场对于Python开发人才的需求量急剧增加,学习Python的前景比较好。 Python应用领域广泛,意味着选择Python的同学在学成之后可选择的就业领域有…...

不懂命令, 如何将代码托管到Gitee上
1.注册码云注册地址 : https://gitee.com2. 新建仓库第一步 : 创建仓库第二步 : 给仓库起名字创建好仓库后, 我们就有了一个网络上的仓库 : 3. 将网络上的仓库克隆到本地在克隆仓库之前, 我们需要先在电脑上安装以下两个工具 >>这两个软件一定要按顺序安装, 先安装第一个…...

Mysql常见面试题总结
1、什么是存储引擎 存储引擎指定了表的类型,即如何存储和索引数据,是否支持事务,同时存储引擎也决定了表在计算机中的存储方式。 2、查看数据库支持哪些存储引擎使用什么命令? -- 查看数据库支持的存储引擎 show engines; 或者 …...

python第一周作业
作业1:1、PPT上五个控制台界面2、要求定义两个数,并且交换它们的值(请使用多种方式,越多越好)作业1作业2:判断一个数,是否是2的指数2的指数0000 0010 0000 00010000 0100 0000 00110000 1000 00…...

FLoyd算法的入门与应用
目录 一、前言 二、FLoyd算法 1、最短路问题 2、Floyd算法 3、Floyd的特点 4、Floyd算法思想:动态规划 三、例题 1、蓝桥公园(lanqiaoOJ题号1121) 2、路径(2021年初赛 lanqiaoOJ题号1460) 一、前言 本文主要…...

303. 区域和检索 - 数组不可变
303. 区域和检索 - 数组不可变 给定一个整数数组 nums,处理以下类型的多个查询: 计算索引 left 和 right (包含 left 和 right)之间的 nums 元素的 和 ,其中 left < right 实现 NumArray 类: NumArray(int[] num…...

Spring Cloud融合Nacos配置加载优先级 | Spring Cloud 8
一、前言 Spring Cloud Alibaba Nacos Config 目前提供了三种配置能力从 Nacos 拉取相关的配置: A:通过内部相关规则(应用名、扩展名、profiles)自动生成相关的 Data Id 配置B:通过 spring.cloud.nacos.config.extension-configs的方式支持…...

LeetCode 236.二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖…...

awk简单实例(持续更新中)
一 概述 awk命令是一种分析和处理文本文件的编程工具。它的功能非常强大,是Linux/Unix系统中最常用的过滤工具。 awk内建变量: NF 整个数据行(即$0)拥有的字段总数 NR 当前awk所处理的数据行的编号 $0 当前awk所处理的数据行 $1 数据行的第1个字段 $2 数…...

react动态路由组件的封装
react动态路由组件的封装 我这篇比较全面 首先下载包 npm i react-router-dom5 这里为什么要用5的版本为啥不用最新的,原因在于老版本跟新版本写法不一样 老版本 import { HashRouter, Route, Switch, Redirect } from react-router-dom;render() {return (<Ha…...

Vue项目中引入高德地图步骤详解
高德地图API官网:高德开放平台 | 高德地图API。 目录 一、案例效果 二、开发准备 1. 注册高德开放平台账号 2. 创建应用添加 key 值 三、项目中使用地图组件 1. npm 获取高德地图 API 2.在项目中新建 MapContainer.vue 文件,用作地图组件。 3.在…...

软件测试用例篇(2)
功能测试界面测试兼容性测试安全测试易用性测试性能测试 针对有需求的案例来设计测试用例:邮箱注册,部分测试用例 https://zay1xofb7z6.feishu.cn/mindnotes/bmncnKD5Ak6GSZl3PRlWDgF9z3g#mindmap 一)等价类: 场景需求:姓名长度是6-200位,那么如何进行设…...

leetcode题解-27. Remove Element
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。 不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 元素的顺序可以改变。你不需要考虑数组中超出新长度后面…...

【fly-iot飞凡物联】(4):在linux系统上搭建arduino环境,可以使用离线包,导入到arduino上即可。
目录前言1,关于2,然后就可以找到ESP32,ESP8266的主版3,方法2,github下载,然后手动添加到ide中吧4,总结前言 本文的原文连接是: https://blog.csdn.net/freewebsys/article/details/108971807 未…...

java实例解析类图中【关联、组合和聚合】的区别
总目录链接==>> AutoSAR入门和实战系列总目录 文章目录 聚合Composition聚合与组合的区别关联是两个独立类之间的关系,它通过它们的对象建立关联。关联可以是一对一、一对多、多对一、多对多。在面向对象的编程中,一个对象与另一个对象通信以使用该对象提供的功能和服…...

基于m-p条件查询代码生成
目录 起因 演示 使用 0.自定义注解 1.定义一个dto的条件查询类 2.调用主程序 效果图 小结 代码 注解 Dto类 完整代码 起因 最近两天一直写后台管理统计的增删改查(很少写增删改查,所以不是很熟练),几乎每个表都要涉及到条件查询的业务…...

【LeetCode】带环链表两道题
第一题:环形链表 问题介绍 给你一个链表的头节点head ,判断链表中是否有环。 如果链表中有某个节点,可以通过连续跟踪next指针再次到达,则链表中存在环。为了表示给定链表中的环,评测系统内部使用整数pos 来表示链表…...

CSS奇思妙想之-利用CSS裁剪(clip-path)完成各种图形
在日常开发当中,如果想要开发多边形,一般都需要多个盒子或者伪元素的帮助,有没有一直办法能只使用一个盒子实现呢? 有的:css裁剪 clip-path介绍 css裁剪(clip-path)这个属性平时率非常低。但是…...

力扣每日一题刷题总结:哈希表篇
剑指 Offer II 033.变位词组 Medium 哈希表 变位词 2023/3/3 给定一个字符串数组 strs ,将 变位词 组合在一起。 可以按任意顺序返回结果列表。 注意:若两个字符串中每个字符出现的次数都相同,则称它们互为变位词。 示例: 示例 1:…...

【Redis】redis大key和大value的危害,如何处理?
前序 还记得上次和同事一起去面试候选人时,同事提了一个问题:Redis的大key有什么危害?当时候选人主要作答的角度是一个key的value较大时的情况,比如: 内存不均:单value较大时,可能会导致节点之…...

Spring Boot:实现MyBatis动态创建表
在有些应用场景中,我们会有需要动态创建和操作表的需求。 比如因为单表数据存储量太大而采取分表存储的情况,又或者是按日期生成日志表存储系统日志等等。这个时候就需要我们动态的生成和操作数据库表了。 而我们都知道,以往我们使用MyBati…...

SpringBoot+Seata在多数据源和feign中的简单使用
SpringBootSeata简单使用 目录seata执行过程安装seata下载seata使用自定义配置文件,NACOS为注册中心结合springboot实现AT模式1.多数据源引入依赖bootstrap.yml配置在使用的方法上用GlobalTransactional注解调用接口正常时调用接口报错时回滚2.配合feignseata优缺点seata执行过…...

计算机网络中的原码、反码、补码
写在前面 原码、反码、补码是计算机组成原理中的概念,是计算机网络的基础知识之一。这些概念是为了处理二进制数的符号位而引入的,常用于计算机中的整数运算,也常用于数据存储和传输等领域。因此,了解和掌握这些概念对于理解计算机…...