【Linux取经路】进程控制——进程等待
文章目录
- 一、进程创建
- 1.1 初识 fork 函数
- 1.2 fork 函数返回值
- 1.3 写时拷贝
- 1.4 fork 的常规用法
- 1.5 fork 调用失败的原因
- 1.6 创建一批进程
- 二、进程终止
- 2.1 进程退出场景
- 2.2 strerror函数
- 2.3 errno全局变量
- 2.4 程序异常
- 2.5 进程常见退出方法
- 2.6 exit 函数
- 2.7 _exit 函数和 exit 函数的区别
- 三、进程等待
- 3.1 进程等待的必要性
- 3.2 什么是进程等待?
- 3.3 进程等待具体是怎么做的?
- 3.3.1 wait方法
- 3.3.2 waitpid方法
- 3.3.3 父进程只等待一个进程(阻塞式等待)
- 3.3.4 父进程等待多个子进程(阻塞式等待)
- 3.4 获取子进程的退出信息(阻塞式等待)
- 3.5 wait、waitpid的实现原理
- 3.6 非阻塞轮询等待
- 四、结语
一、进程创建
1.1 初识 fork 函数
在 Linux 中,fork 函数用于从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。
#include <unistd.h>
pid_t fork(void); // fork 函数声明
返回值:子进程中返回0;父进程中返回子进程的 pid,出错返回-1。
一个进程调用 fork 函数后,当控制转移到内核中的 fork 代码后(执行 fork 函数的代码),内核做了如下一些工作:
-
分配新的内存块和内核数据结构给子进程。
-
将父进程部分数据结构内容拷贝到子进程中。
-
添加子进程到系统进程列表当中。
-
fork 返回,开始调度器调度。
小Tips:其实做完前两步,子进程就已经被创建出来了。
当一个进程调用 fork 之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都将可以开始它们自己的旅程。说的再多还是需要通过代码来演示证明。
#include <stdio.h>
#include <unistd.h> int main()
{ printf("befor pid:%d\n", getpid()); fork(); printf("after pid:%d\n", getpid()); return 0;
}
这里打印了三行输出,一行是 befor 两行是 after。所以,fork 之前父进程独立执行,fork 之后,父子两个执行流分别执行。注意,fork 之后,谁先执行完全由调度器决定。
1.2 fork 函数返回值
-
子进程返回0。
-
父进程中返回子进程的 pid,出错返回-1。
关于 fork 函数的返回值问题,在【Linux取经路】揭秘进程的父与子一文中已做了详细介绍,其中包括“为什么给子进程返回0,给父进程返回 pid”、“fork 函数是如何做到返回两次的”。感兴趣的小伙伴可以点回去看看。
1.3 写时拷贝
通常,父子进程代码共享,父子进程再不写入的时候,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式再生成一份。
操作系统是如何知道要进行写时拷贝的呢?答案是:父进程在创建子进程的时候,操作系统会把父子进程页表中的数据项从读写权限设置成只读权限,此后父进程和子进程谁要对数据进行写入就一定会触发权限方面的问题,在进行权限审核的时候,操作系统会识别出来,历史上要访问的这个区域是可以被写入的,只不过暂时是只读状态,父子进程不管谁尝试对数据区进行写入的时候都会触发权限问题,但是针对这这种情况操作系统并不做异常处理,而是把数据拷贝一份,谁写的就把页表项进行重新映射,在拷贝完成后,就把只读标签重新设置成可读可写。
操作系统为什么要采用写时拷贝呢?父进程在创建子进程的时候,单纯的从技术角度去考虑,操作系统完全可以让父子进程共享同一份代码,然后把父进程的多有数据全部给子进程拷贝一份,技术上是完全可以实现的,但是操作系统为什么没有这样干?而是采用写时拷贝呢?原因主要有以下几点,首先假设父进程中国有100个数据,子进程只需要对其中的一个进行修改,剩下的99个子进程只读就可以,那如果操作系统把这100个数据全给子进程拷贝了一份,无疑是干了一件吃力不讨好的工作,全部拷贝既浪费了时间又浪费的物理内存,操作系统是绝对不会允许这种情况发生的,因此,对于数据段,操作系统采用的是写时拷贝的策略。
1.4 fork 的常规用法
-
一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端的请求,生成子进程来处理请求。
-
一个进程要执行一个不同的程序。例如子进程从 fork 返回后,调用 exec 函数。
1.5 fork 调用失败的原因
-
系统中有太多的进程。
-
实际用户的进程数超过了限制。
1.6 创建一批进程
通过 for 循环创建一批进程。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h> #define N 5 void func()
{ int cnt = 10; while(cnt) { printf("I am chid, pid:%d, ppid:%d\n", getpid(), getppid()); cnt--; sleep(1); } return;
} int main()
{ int i = 0; for(i = 0; i < N; i++) { pid_t id = fork(); if(id == 0)// 只有子进程会进去 { func(); exit(0);// 子进程走到这里就退出了 } } sleep(1000); return 0;
}
小Tips:父进程执行的速度是很快的,由于父进程的 for 循环里没有 sleep 函数,所以五个子进程几乎是在同一时间被创建出来,创建出来的每一个子进程会去调用 func 函数,每一个子进程执行完 func 函数后会执行 exit 函数退出。父子进程谁先执行完全是由调度器来决定的。
二、进程终止
2.1 进程退出场景
-
代码运行完毕,结果正确
-
代码运行完毕,结果不正确
-
代码异常终止
一般代码运行完毕,结果正确,我们是不会关心代码为什么跑对了。但是当代码运行完毕,结果不正确,我们作为程序员是需要知道为什么结果不正确,因此进程需要将运行结果以及不正确的原因告诉程序员。这就是 main 函数里常写的 return 0
的作用。return 后面跟的数字叫做进程的退出码,表征进程的运行结果是否正确,不同的返回数字表征不同的出错原因,0表示 success。main 函数 return 的这个0,最终会被父进程,即 bash 拿到。可以在 bash 中输出 echo $?
指令查看上一个子进程的退出码。$?
表示命令行当中最近一个进程运行的退出码。
int main()
{ printf("模拟一段逻辑!\n"); return 0;
}
小Tips:对于一个进程,一般而言只有父进程最关心它的运行情况
2.2 strerror函数
上面提到的退出码本质上是数字,它更适合机器去查看,作为程序员我们可能对数字没有那么敏感,即可能不知道该数字表示的是什么意思。因此 strerror
函数的作用就是将一个退出码转换成为一个错误信息描述。可以通过下面这段代码来打印当前系统支持的所有错误码对应的错误信息。
int main()
{ int i = 0; for(; i < 200; i++) { printf("%d, %s\n", i, strerror(i)); } return 0;
}
2.3 errno全局变量
errno 是 C 语言给我们提供的一个全局变量,它里面保存的是最近一次执行的错误码,何谓最近一次执行?C 语言为我们提供了很多的库函数,在调用这些库函数失败的时候,C 语言就会将 errno 设置成对应的数字,这个数字就表示调用该函数出错的错误码。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h> int main()
{ int ret = 0; char* str = (char*)malloc(1000*1000*1000*4); if(str == NULL) { printf("malloc error:%d, %s\n", errno, strerror(errno)); ret = errno; } else { printf("malloc success!\n"); } return ret;
}
2.4 程序异常
代码如果出现了异常,本质上代码可能就没有跑完,因此可能就没有执行 return 语句。所以程序如果出现了异常,那么该程序的退出码是没有意义的。因此对于一个执行结束的进程来说,我们要先看它是否出异常,如果没有异常再去看它的退出码是否正确。对于异常我们也需要知道程序为什么异常,以及发生了什么异常。进程出现异常,本质上是我们的进程收到了对应的信号。像程序中除0,空指针解引用,一般都会引发硬件错误,由我们的操作系统向对应的进程发送信号。Linux 系统的所有信号如下图所示。
int main()
{ char* pc = NULL; *pc = 'a'; // 解引用空指针,会发生段错误return 0;
}
下面证明该异常是因为程序收到了对应的信号。
2.5 进程常见退出方法
正常终止(指程序的代码执行完了结束,而不是收到信号结束)。
-
从 main 函数返回,即 return
-
调用 exit 函数
-
_exit
异常退出。
-
ctrl+c
-
信号终止
2.6 exit 函数
#include <unistd.h>
void exit(int status);
在代码中的任何地方调用 exit
函数,都表示调用进程直接退出。退出码就是 exit
函数的参数 status
。说这个主要是为了区分 return
和 exit
,return 只有在主函数(main)中出现才表示进程退出,在普通的函数中使用 return 仅表示函数返回,而在函数中使用 exit,也会让进程直接退出。
2.7 _exit 函数和 exit 函数的区别
上面的现象我们是可以理解的,printf 函数后面没有加 \n
,因此要打印的内容先被保存在了缓冲区中,等休眠两秒后,程序执行 exit
退出,程序退出会刷新缓冲区,所以程序运行我们看到的效果是前两秒什么也没打印,在程序退出前才执行了打印。下面我们把 exit
换成 _exit
再看看效果。
这次程序执行后,等待了两秒直接退出了,并没有将信息打印出来。
结论:_exit
是系统调用,exit
是库函数。exit 最后会调用 _exit,但是在调用 _exit 之前,还做了下面几个工作。
-
执行用户通过 atexit 或 on_exit 定义的清理函数。
-
关闭所有打开的流,所有的缓冲区数据均被写入。
-
调用 _exit()。
小Tips:通过上面的现象我们可以的出一个结论,那就是缓冲区一定不在内核中,而是在用户空间。因为如果在内核中,那调用 _exit 函数的时候,也必然会把缓冲区中的数据进行刷新,如果不刷新,那还维护这个缓冲区干嘛呢?正是因为缓冲区在用户区,_exit 作为系统调用看不到用户区的数据,所以才没办法刷新。
三、进程等待
3.1 进程等待的必要性
-
在前面的文章中讲过,子进程退出,父进程如果不管不顾,就可能造成“僵尸进程”的问题,进而会造成内存泄露。
-
另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的
kill -9
指令也无能为力,因为谁也没有办法杀死一个已经死去的进程。 -
最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。
-
父进程通过进程等待的方式,回收子进程资源,获取子进程的退出信息。
总结:僵尸进程无法被杀死,需要通过进程等待来杀掉它,进而解决内存泄露的问题,这是进程等待的必要性。其次通过进程等待,让父进程获得子进程的退出情况,看布置的任务完成的怎么样了,这一点对父进程来说是可选项,即父进程也可以选择不关心,如果要关心了,需要通过进程等待去获取。
3.2 什么是进程等待?
进程等待就是在父进程的代码中,通过系统调用 wait/waitpid
,来进行对子进程进行状态检测与回收的功能。
3.3 进程等待具体是怎么做的?
3.3.1 wait方法
#include <sys/types.h>
#include <sys/wait.h>pid_t wait(int* status);
-
返回值:成功,返回被等待进程的 pid,失败返回-1。
-
参数:输出型参数,获取子进程的退出状态,不关心则可以设置成为 NULL。
3.3.2 waitpid方法
#include <sys/types.h>
#include <sys/wait.h>pid_t waitpid(pid_t pid, int* status, int options);
-
返回值:当正常返回的时候
waitpid
返回等待到的子进程的进程 ID;如果设置了选项WNOHANG
,而调用的过程中没有子进程退出,则返回0;如果调用中出错,则返回-1,这时errno
会被设置成相应的值以指示错误所在。 -
参数
pid
:pid = -1
表示等待任意一个子进程。与wait
等效;pid > 0
表示等待进程 ID 与pid
相等的子进程。 -
参数
status
:WIFEXITED(status)
:查看子进程是否正常退出。若为正常终止子进程返回的状态,则为真;WEXITSTATUS(status)
:查看进程的退出码。若非零,提取子进程的退出码。 -
参数
options
:0:表示父进程以阻塞的方式等待子进程,即子进程如果处在其它状态,不处在僵尸状态(Z状态),父进程会变成 S 状态,操作系统会把父进程放到子进程 PCB 对象中维护的等待队列中,以阻塞的方式等待子进程变成僵尸状态,当子进程运行结束,操作系统会检测到,把父进程重新唤醒,然后回收子进程;WNOHANG
:非阻塞轮询等待,若 pid 指定的子进程没有结束,处于其它状态,则waitpid()
函数返回0,不予等待。若正常结束,则返回该子进程的 ID。
小Tips:wait 和 waitpid 都只能等待该进程的子进程,如果等待了其它的进程那么就会出错。
3.3.3 父进程只等待一个进程(阻塞式等待)
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h> int main()
{ pid_t id = fork(); if(id < 0) { perror("fork"); return 1; } else if(id == 0) { // child int cnt = 5; while(cnt) { printf("I am child, pid:%d, ppid:%d, cnt:%d\n", getpid(), getppid(), cnt--); sleep(1); } exit(0); } else { int cnt = 10; // parent while(cnt) { printf("I am parent, pid:%d, ppid:%d, cnt:%d\n", getpid(), getppid(), cnt--); sleep(1); } int ret = wait(NULL); if(ret == id) { printf("wait success!\n"); } sleep(5); } return 0;
}
结果分析:前五秒父子进程同时运行,紧接着子进程退出变成僵尸状态,五秒钟后父进程对子进程进行了等待,成功将子进程释放掉,最后再五秒钟后父进程也退出,整个程序执行结束。
3.3.4 父进程等待多个子进程(阻塞式等待)
一个 wait
只能等待任意一个子进程,因此父进程如果要等待多个子进程可以通过循环来多次调用 wait
实现等待多个子进程。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h> #define N 5
// 父进程等待多个子进程
void RunChild()
{ int cnt = 5; while(cnt--) { printf("I am child, pid:%d, ppid:%d\n", getpid(), getppid()); sleep(1); } return;
}
int main()
{ for(int i = 0; i < N; i++) { pid_t id = fork();// 创建一批子进程 if(id == 0) { // 子进程 RunChild(); exit(0); } // 父进程 printf("Creat process sucess:%d\n", id); } sleep(10); for(int i = 0; i < N; i++) { pid_t id = wait(NULL); if(id > 0) { printf("Wait process:%d, success!\n", id); } } sleep(5); return 0;
}
小Tips:如果子进程不退出,父进程在执行 wait
系统调用的时候也不返回(默认情况),默认叫做阻塞状态。由此可以看出,一个进程不仅可以等待硬件资源,也可以等待软件资源,这里的子进程就是软件。
3.4 获取子进程的退出信息(阻塞式等待)
在 2.1 小结提到过,进程有三种退出场景。正是因为有这三种退出场景,父进程等待希望获得子进程退出的以下信息:子进程代码是否异常;没有异常,结果对嘛?不对是因为什么呢? 子进程这些所有的退出信息都被保存在 status
参数里面。
-
wait
和waitpid
都有一个status
参数,该参数是一个输出型参数,由操作系统填充。 -
如果传递 NULL,表示不关心子进程的退出状态信息。
-
否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
-
status
不能简单的当做整形来看待,可以当做位图来看待,具体细节如下图(只需要关注 status 低16比特位)
小Tips:操作系统没有0号信号,因此,如果低七位是0说明子进程没有收到任何信号。
int main()
{pid_t id = fork();if(id < 0){perror("fork");return 1;}else if(id == 0){// childint cnt = 5, a = 10;while(cnt){printf("I am child, pid:%d, ppid:%d, cnt:%d\n", getpid(), getppid(), cnt--);sleep(1);a /= 0; // 故意制造一个异常}exit(11); // 将退出码故意设置成11}else {// parentint cnt = 10;while(cnt){printf("I am parent, pid:%d, ppid:%d, cnt:%d\n", getpid(), getppid(), cnt--);sleep(1); }// 目前为止,进程等待是必须的!//int ret = wait(NULL);int status = 0;int ret = waitpid(id, &status, 0);if(ret == id){// 获取子进程退出状态信息的关键代码// 0111 1111:0x7F,1111 1111 0000 0000:0xFF00printf("wait success! exit signal:%d, exit code:%d!\n", status&0X7F, (status >> 8)&0XFF); }sleep(5);}return 0;
}
小Tips:通过运行结果可以看出,子进程收到了8号信号,子进程的退出码是0。代码中子进程的退出码被我们设置成了11,这侧面印证了我们上面讲到的,进程收到信号后被异常终止,此时代码没有执行完毕,所以此时进程的退出码是不可信的。
// 常规的进程等待代码
int status = 0;
int ret = waitpid(id, &status, 0);
if(ret == id)
{// 0111 1111:0x7F,1111 1111 0000 0000:0xFF00//printf("wait success! exit signal:%d, exit code:%d!\n", status&0X7F, (status >> 8)&0XFF);if(WIFEXITED(status)){printf("子进程正常退出,退出码是:%d\n", WEXITSTATUS(status));}else {printf("子进程被异常终止!\n");}
}
3.5 wait、waitpid的实现原理
一个进程在退出后,父进程回收之前,它的代码和数据都被释放了,但是它的 PCB 对象并没有被释放,因为它收到的信号和退出码信息都保存在 PCB 对象中,wait 和 waitpid 本质上就是操作系统去检查一个进程是否处于僵尸状态(Z状态),如果处于 Z 状态就去它的 PCB 对象中拿到该进程收到的信号和退出码信息,再把这些信息赋值给 status,然后将该进程的状态设置成 X。这个工作只能由操作系统来做,因为 PCB 对象属于内核数据结构对象,不允许用户直接访问。
3.6 非阻塞轮询等待
前面说过,若父进程采用阻塞式等待,如果子进程没有处于僵尸状态,那么此时父进程处于阻塞状态什么也干不了。若父进程采用非阻塞轮询等待,如果子进程没有处于僵尸状态,那么父进程可以继续去干它的事情。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>// 父进程只等待一个子进程(非阻塞轮询等待)
int main()
{pid_t id = fork();if(id < 0){perror("fork");return 1;}else if(id == 0){// childint cnt = 5;while(cnt){printf("I am child, pid:%d, ppid:%d, cnt:%d\n", getpid(), getppid(), cnt--);sleep(1);//a /= 0;}exit(11);}else {// parent // 目前为止,进程等待是必须的!//int ret = wait(NULL);while(1){int status = 0;int ret = waitpid(id, &status, WNOHANG);if(ret > 0){// 0111 1111:0x7F,1111 1111 0000 0000:0xFF00//printf("wait success! exit signal:%d, exit code:%d!\n", status&0X7F, (status >> 8)&0XFF);if(WIFEXITED(status)){printf("子进程正常退出,退出码是:%d\n", WEXITSTATUS(status));}else {printf("子进程被异常终止!\n");}break;}else if(ret == 0){// 父进程的任务可以写在这里printf("child process is running...\n");}else{printf("等待出错!\n");}sleep(1);}sleep(2);}return 0;
}
小Tips:在非阻塞轮询等待过程中父进程可以去执行自己的任务,前提是该任务轻量化且可返回,非阻塞轮询等待的核心任务还是回收子进程。子进程创建出来父子进程谁先执行是由调度器说了算,进程等待在一定程度上确保了父进程一定是最后一个退出的,这样可以避免子进程变为僵尸进程,进而导致内存泄露的问题。
四、结语
今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,春人的主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是春人前进的动力!
相关文章:

【Linux取经路】进程控制——进程等待
文章目录 一、进程创建1.1 初识 fork 函数1.2 fork 函数返回值1.3 写时拷贝1.4 fork 的常规用法1.5 fork 调用失败的原因1.6 创建一批进程 二、进程终止2.1 进程退出场景2.2 strerror函数2.3 errno全局变量2.4 程序异常2.5 进程常见退出方法2.6 exit 函数2.7 _exit 函数和 exit…...

虹科干货 | 如何使用nProbe Cento构建100 Gbit NetFlow 传感器
本文是一份全面的指南,解释了如何使用nProbe Cento构建一个高效的100 Gbit NetFlow传感器。旨在帮助大家充分利用NetFlow技术,以监控和分析高速网络流量。 当需要监控分布式网络,了解流经上行链路或关键网段的网络流量时,NetFlow…...

Web前端入门 - HTML JavaScript Vue
ps:刚开始学习web前端开发,有什么不正确、不标准的内容,欢迎大家指出~ Web简介 90年代初期,Web1.0,静态页面,不和服务器交互,网页三剑客指Dreamweaver、Fireworks、Flash2000年代中期…...

(Sping Xml方式整合第三方框架)学习Spring的第十天
Spring整合mybatis 1 . 导入Mybatis整合Spring的相关坐标 <dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.2.13.RELEASE</version></dependency><dependency><…...

单片机驱动多个ds18b20
目录 1设计内容 2ds18b20介绍 2.1传感器引脚及原理图 2.2寄存器配置 3程序实现 3.1配置初始化 3.2配置寄存器 3.3ROM读取 3.4温度读取 1设计内容 通过51单片机,读取总线上挂载的多个ds18b20的温度信息。 如下图,成功读取到3路温度数据。 2ds18…...

GitLab16.8配置webhooks、Jenkins2.4配置GitLab插件实现持续集成、配置宝塔面板实现持续部署(其三)
看本篇文章的前提是已经部署完GItlab和Jenkins服务器,已经可以手动构建成功,并且经过了很多次实践,对这两款软件基本熟悉。 建议大家按以下顺序看 前端自动化(其一)部署gitlab 前端自动化(其二࿰…...

鸿蒙会取代Android吗?听风就是雨
现在说取代还谈不上,毕竟这需要时间。安卓作为全球第一的手机操作系统,短时间内还无法取代。持平iOS甚至超过iOS有很大可能,最终会呈现“三足鼎立”有望超过安卓基数。 作为全新的鸿蒙操作系统,其现在已经是全栈自研底座。按照鸿…...

检测CUDA 是否能访问GPU时回应速度慢【笔记】
SUPWEMICRO 418G-Q20X12 维护记录: 两台设备均已安装CUDA与Pytorch,在检测CUDA 是否能访问GPU,执行torch.cuda.is_available()命令时,一台设备速度秒回应True,但另外一台设备回应速度慢(1分钟左右ÿ…...

大模型运行成本对比:GPT-3.5/4 vs. 开源托管
在过去的几个月里,生成式人工智能领域出现了许多令人兴奋的新进展。 ChatGPT 于 2022 年底发布,席卷了人工智能世界。 作为回应,各行业开始研究大型语言模型以及如何将其纳入其业务中。 然而,在医疗保健、金融和法律行业等敏感应用…...

fastadmin后台自定义按钮和弹窗
工具栏自定义按钮-ajax请求 前端代码 1.在对应模块的模板文件index.html添加自定义按钮,注意按钮要添加id以绑定点击事件 <div class"panel panel-default panel-intro">{:build_heading()}<div class"panel-body"><div id&qu…...

《高性能MySQL》
文章目录 一、创建1. 磁盘1.1 页、扇区、寻道、寻址、硬盘性能 2. 行结构row_format2.1 Compact紧凑2.1.1 行溢出2.1.2 作用2.1.3 内容1-额外信息1、变长字段长度2、NULL值列表3、记录头信息 2.1.4 内容2-真实数据4、表中列的值5、transaction_id6、roll_point7、row_id 2.2 dy…...

postman用法
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、postman怎么使用json输出 总结 前言 提示:这里可以添加本文要记录的大概内容: 提示:以下是本篇文章正文内容࿰…...
MySQL之数据库DQL
文章目录 数据查询DQL基本查询运算符算数运算符比较运算符逻辑运算符位运算符 排序查询聚合查询分组查询分页查询INSERT INTO SELECT语句SELECT INTO FROM语句 数据查询DQL 数据库管理系统一个重要功能就是数据查询,数据查询不应只是简单返回数据库中存储的数据&am…...

《区块链简易速速上手小册》第9章:区块链的法律与监管(2024 最新版)
文章目录 9.1 法律框架和挑战9.1.1 基础知识9.1.2 主要案例:加密货币的监管9.1.3 拓展案例 1:跨国数据隐私和合规性9.1.4 拓展案例 2:智能合约的法律挑战 9.2 区块链的合规性问题9.2.1 基础知识9.2.2 主要案例:加密货币交易所的合…...

Spring Boot 中操作 Bean 的生命周期
1.InitializingBean和DisposableBean InitializingBean接口提供了afterPropertiesSet方法,用于在bean的属性设置好之后调用; DisposableBean接口提供了destroy方法,用于在bean销毁之后调用; public class TestComponent implem…...

Linux ---- Shell编程三剑客之AWK
一、awk处理文本工具 1、awk概述 awk 是一种处理文本文件的语言,是一个强大的文本分析工具。AWK是专门为文本处理设计的编程语言,也是行处理软件,通常用于扫描、过滤、统计汇总工作。用来处理列。数据可以来自标准输入也可以是管道或文件。…...
Netty入门使用
为什么会有Netty? NIO 的类库和 API 繁杂,使用起来比较麻烦,需要熟练掌握 Selector、ServerSocketChannel、SocketChannel、ByteBuffer 等。开发工作量和难度都非常大,例如客户端面临断线重连、网络闪断、心跳处理、半包读写、网络拥塞和异…...

go并发编程-runtime、Channel与Goroutine
1. runtime包 1.1.1. runtime.Gosched() 让出CPU时间片,重新等待安排任务(大概意思就是本来计划的好好的周末出去烧烤,但是你妈让你去相亲,两种情况第一就是你相亲速度非常快,见面就黄不耽误你继续烧烤,第二种情况就是你相亲速度…...
HTTP概述
HTTP概述 HTTP(Hypertext Transfer Protocol)是一种用于传输超文本的应用层协议。它是在客户端和服务器之间进行通信的基础,常用于 Web 应用中。在 Java 后端开发中,HTTP 扮演着重要的角色。以下是Java 后端视角下的 HTTP 概述&a…...

ubuntu20配置mysql8
首先更新软件包索引运行 sudo apt update命令。然后运行 sudo apt install mysql-server安装MySQL服务器。 安装完成后,MySQL服务将作为systemd服务自动启动。你可以运行 sudo systemctl status mysql命令验证MySQL服务器是否正在运行。 连接MySQL 当MySQL安装…...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...
AI编程--插件对比分析:CodeRider、GitHub Copilot及其他
AI编程插件对比分析:CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展,AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者,分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...

vue3+vite项目中使用.env文件环境变量方法
vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量,这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...

10-Oracle 23 ai Vector Search 概述和参数
一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI,使用客户端或是内部自己搭建集成大模型的终端,加速与大型语言模型(LLM)的结合,同时使用检索增强生成(Retrieval Augmented Generation &#…...

JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...

GruntJS-前端自动化任务运行器从入门到实战
Grunt 完全指南:从入门到实战 一、Grunt 是什么? Grunt是一个基于 Node.js 的前端自动化任务运行器,主要用于自动化执行项目开发中重复性高的任务,例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...
【前端异常】JavaScript错误处理:分析 Uncaught (in promise) error
在前端开发中,JavaScript 异常是不可避免的。随着现代前端应用越来越多地使用异步操作(如 Promise、async/await 等),开发者常常会遇到 Uncaught (in promise) error 错误。这个错误是由于未正确处理 Promise 的拒绝(r…...

客户案例 | 短视频点播企业海外视频加速与成本优化:MediaPackage+Cloudfront 技术重构实践
01技术背景与业务挑战 某短视频点播企业深耕国内用户市场,但其后台应用系统部署于东南亚印尼 IDC 机房。 随着业务规模扩大,传统架构已较难满足当前企业发展的需求,企业面临着三重挑战: ① 业务:国内用户访问海外服…...

初探用uniapp写微信小程序遇到的问题及解决(vue3+ts)
零、关于开发思路 (一)拿到工作任务,先理清楚需求 1.逻辑部分 不放过原型里说的每一句话,有疑惑的部分该问产品/测试/之前的开发就问 2.页面部分(含国际化) 整体看过需要开发页面的原型后,分类一下哪些组件/样式可以复用,直接提取出来使用 (时间充分的前提下,不…...