【Linux】进程控制2——进程等待(waitwaitpid)

news2024/11/23 23:28:04

1. 进程等待必要性

  • 我们知道,子进程退出,父进程如果不管不顾,就可能造成"僵尸进程”的问题,进而造成内存泄漏
  • 另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
  • 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。
  • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

2. 进程等待的方法

2.1 wait方法

在Linux中,wait函数是一个系统调用用于等待子进程的终止并获取其终止状态。该函数的原型如下所示:

#include<sys/types.h>
#include<sys/wait.h>

pid_t wait(int*status);

返回值:
 成功返回被等待进程pid,失败返回-1。

参数:
 输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

        函数功能是:父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。

        当子进程终止后,wait函数会返回子进程的进程ID(PID),并将子进程的终止状态存储在指针status指向的变量中。

        status参数是一个指向整型变量的指针,用于存储子进程的终止状态。通过status可以获取子进程的退出状态、终止信号等信息如果不关心终止状态,可以将status设置为NULL。

wait函数返回的PID有以下几种可能的取值:

  • 如果成功等待到一个子进程的终止,返回子进程的PID。
  • 如果调用进程没有子进程,wait函数会返回-1
  • 如果调用进程被一个信号中断,wait函数会返回-1

 如何使用wait进行等待?

  1. 调用wait函数,进程等待子进程的退出。
  2. 当子进程退出后,会变成一个僵尸进程(短暂的存在不会造成什么影响),然后通过wait函数,进程状态从僵尸状态(Z)变成死亡状态(X)。
  3. 如果子进程没有退出,父进程必须阻塞等待,直到子进程变成Z,wait自动回收返回。
#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <cstdlib>
int main()
{
    pid_t fd = fork();
    if (fd == 0)
    {
        sleep(5);
        std::cout << "child: " << getpid() << std::endl;
        exit(0);
    }
    if (fd > 0)
    {
        sleep(1);
        std::cout << "parent: " << getpid() << std::endl;
    }
    sleep(50);
    return 0;
}

子进程退出后由于父进程没有等待回收,子进程变成僵尸进程: 

调用wait后,子进程就会回收释放了:

#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <cstdlib>
int main()
{
    pid_t fd = fork();
    if (fd == 0)
    {
        sleep(5);
        std::cout << "child: " << getpid() << std::endl;
        exit(0);
    }
    if (fd > 0)
    {
        sleep(1);
        std::cout << "parent: " << getpid() << std::endl;
        wait(NULL);
    }
    sleep(50);
    return 0;
}

我们再深入理解一下

注:

  当父进程忘了用wait()函数等待已终止的子进程时,子进程就会进入一种无父进程的状态,此时子进程就是僵尸进程.

  wait()要与fork()配套出现,如果在使用fork()之前调用wait(),wait()的返回值则为-1,正常情况下wait()的返回值为子进程的PID.

  如果先终止父进程,子进程将继续正常进行,只是它将由init进程(PID为1)继承,当子进程终止时,init进程捕获这个状态.

  参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何死掉毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL,就像下面这样:

pid = wait(NULL);

如果成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD。

  如果参数status的值不是NULL,wait就会把子进程退出时的状态取出并存入其中, 这是一个整数值(int),指出了子进程是正常退出还是被非正常结束的,以及正常结束时的返回值,或被哪一个信号结束的等信息。

        由于这些信息 被存放在一个整数的不同二进制位中,所以用常规的方法读取会非常麻烦,人们就设计了一套专门的宏(macro)来完成这项工作,下面我们来学习一下其中最常用的两个:

  • 1,WIFEXITED(status) :这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个非零值。

        (请注意,虽然名字一样,这里的参数status并不同于wait唯一的参数——指向整数的指针status,而是那个指针所指向的整数,切记不要搞混了。)

  • 2, WEXITSTATUS(status): 当WIFEXITED返回非零值时,我们可以用这个宏来提取子进程的返回值,如果子进程调用exit(5)退出,WEXITSTATUS(status) 就会返回5;如果子进程调用exit(7),WEXITSTATUS(status)就会返回7。

        请注意,如果进程不是正常退出的,也就是说, WIFEXITED返回0,这个值就毫无意义。

我们看个例子 

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main() {
    pid_t id = fork();
    if (id == 0) {
        // 子进程
        printf("子进程开始执行\n");
        sleep(3);
        printf("子进程执行完毕\n");
        exit(0);
    } else if (id > 0) {
        // 父进程
        printf("父进程等待子进程终止\n");
        int status;
        pid_t child_pid = wait(&status);
        
        if (child_pid == -1)
         {
            perror("wait");
            exit(1);
        }
        if (WIFEXITED(status)) 
        {
            printf("子进程正常终止,退出状态:%d\n", WEXITSTATUS(status));
        } else if (WIFSIGNALED(status)) 
        {
            printf("子进程被信号终止,信号编号:%d\n", WTERMSIG(status));
        }
        	printf("父进程继续执行\n");
	    } 
	    else 
	    {
	        perror("fork");
	        exit(1);
	    }
    return 0;
}

在上面的示例中,父进程通过fork创建了一个子进程。

子进程会执行一段耗时的操作(这里使用sleep模拟),然后退出。

父进程调用wait函数等待子进程的终止,并获取子进程的终止状态。

最后,父进程继续执行。 

2.2.参数status

2.2.1、status 参数是位图结构 

wait 和 waitpid,都有这个 status 参数,如果传递 NULL,则表示不关心子进程的退出状态信息。否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。 

该参数是一个 输出型参数 (即通过调用该函数,从函数内部拿出来特定的数据)。

什么叫输出型参数?就是函数调用结束以后,会将参数的值写到这个变量里。

        换句话说,这个status是用来接收的,本质上不是用来传参的。

        我们把我们的status定义好了之后,放到该函数里,作为参数传递过去,函数调用完后,操作系统就会把status的值自动填充好,然后还给我们。实现的原理很简单,因为其用的是指针,传递的是变量的地址。倘若我们不关心这个status状态,那么直接传递NULL即可。

并且,status 参数是由操作系统填充的!是一个整数,该整数就是下面我们要详细研究的。

也就是说我们下面说的参数status不是wait唯一的参数——指向整数的指针status,而是那个指针所指向的整数

它虽然是一个 int 型整数,但是不能简单地将其看作整型,而是被当作一个 位图结构 看待。

不过,关于 status 我们只需要关心该整数的 低 16 个比特位!

我们不必去关心它的高 16 位,因为凭借低 16 位就足以判断了。

然而,整数的低 16 位,其中又可以分为 最低八位 和 次低八位(具体细节看图):

我们之研究status的低16比特位

最低八位(包括core dump)存储的是终止信号,次低八位存储的是退出状态

 2、次低八位:拿子进程退出码

重点:通过提取 status 的次低八位,就可以拿到子进程的退出码。 

我们需要使用下面的代码来解析:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
 
int main () 
{
    pid_t id = fork();
    if (id == 0) {
        int cnt = 5;   // 循环5次
        // child
        while (cnt--) {
            // 五秒之内运行状态
            printf("我是子进程,我正在运行... Pid: %d\n", getpid());
            sleep(1);
 
            // 五秒之后子进程终止
            
        }
 
        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
            );
        }
 
    }
}

我们说了,status 并不是整体使用的,而是区域性使用的,我们要取其次低八位。

我们可以用 位操作 来完成,将 status 右移八位再按位与上 \textrm{0xFF},即 (status>>8)&0xFF ,就可以提取到 status 的次低八位了。

 

3、 最低七位:提取子进程的退出信号

重点:通过提取 status 的最低七位,就可以拿到子进程的退出信号。

我们的 status 的低八位用于表示处理异常的地方,其中有 1 位是 core dump,我们下面讲。

除去 core dump,剩余七位用于进程中的退出信号,这就是 最低七位。

进程退出,如果异常退出,是因为这个进程收到了特定的信号。

我们虽然还没有开始讲解信号,但是我们前几张就介绍了 kill -9 这样的杀进程操作。

这个 -9 我们当时说了,就是一个信号,发送该信号也确实可以终止进程。

刚才我们讲的 wait/waitpid 和次低八位的时侯,都是关于进程的 正常退出。

如果进程 异常退出 呢?

我们来模拟一下进程的异常退出。

结果:

因为子进程是个死循环,父进程又调了 waitpid,导致父进程一直在 "阻塞式" 地等待子进程。

父进程在等待子进程期间什么都没有干,就搬了张板凳坐在那等子进程死。

信号是可以杀掉进程的,我们现在主动输入 kill -9:

此时我们就成功拿到了子进程的退出信号,9 是因为我们输入的信号就是 9。

此时父进程看到子进程寄了,终于可以不用等了,可以给子进程收尸了

还是那句话,代码跑完结果是什么已经不重要了,我们最关心的是因为什么原因退出的。

当进程收到信号时,就代表进程异常了。进程程出,如果是异常退出,是因为该进程收到了特定的信号。其实除了 9 号信号还有很多信号,输入 kill -l 就可以查看这些;

总结:退出信号代表进程是否异常,退出码代表进程在退出之时代码对还是不对。

4、进程退出的宏

我们今天写的代码,是通过位操作去截 status 得到退出码和退出信号的。

实际上,你也可以不用位操作,因为  已经给我们提供了一些宏供我们直接调用。

它们是 WEXITSTATUS 和 WIFEXITED,在这之前,我们再思考一个问题:

思考:一个进程退出时,可以拿到退出码和退出信号,我们先看谁?

        一旦程序发现异常,我们只关心退出信号,退出码没有任何意义。

        所以,我们先关注退出信号,如果有异常了我们再去关注退出码

WEXITSTATUS 宏用于查看进程的退出码,若非 0,提取子进程退出码。

WEXITSTATUS(status)

WIFEXITED 宏用于查看进程是否正常退出,如果是正常终止的子进程返回状态,则为真。

WIFEXITED(status)

结果是

当然了,如果你压根就不关注推出信息和退出码,你直接把 status 设置为 NULL 就行。

2.3. waitpid方法

waitpid函数是Linux中用于等待指定子进程终止的系统调用

        与wait函数类似,waitpid函数也可以用于获取子进程的终止状态。

#include <sys/types.h>
#inlclude <sys/wait.h>

pid_ t waitpid(pid_t pid, int *status, int options);

        函数功能是:父进程一旦调用了waitpid就立即阻塞自己,由waitpid自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,waitpid就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,waitpid就会一直阻塞在这里,直到有一个出现为止。 

从本质上讲,系统调用waitpid和wait的作用是完全相同的,但waitpid多出了两个可由用户控制的参数pid和options,从而为我们编程提供了另一种更灵活的方式。

下面我们就来详细介绍一下这两个参数:

pid:

从参数的名字pid和类型pid_t中就可以看出,这里需要的是一个进程ID。但当pid取不同的值时,在这里有不同的意义。

  1.   pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
  2.   pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。
  3.   pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。
  4.   pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。

options:

options提供了一些额外的选项来控制waitpid,目前在Linux中只支持WNOHANG和WUNTRACED两个选项,这是两个常数,可以用"|"运算符把它们连接起来使用,比如:

  ret = waitpid(-1,  NULL,  WNOHANG | WUNTRACED);

如果我们不想使用它们,也可以把options设为0,如:

     ret = waitpid(-1,  NULL,  0);

如果使用了WNOHANG参数调用waitpid,即使没有子进程退出,它也会立即返回,不会像wait那样永远等下去。

而WUNTRACED参数,由于涉及到一些跟踪调试方面的知识,加之极少用到,这里就不多费笔墨了,有兴趣的读者可以自行查阅相关材料。

看到这里,聪明的读者可能已经看出端倪了:wait不就是经过包装的waitpid吗?

没错,察看<内核源码目录>/include/unistd.h文件349-352行就会发现以下程序段:

static inline pid_t wait(int * wait_stat)

{

    return waitpid(-1,wait_stat,0);

}

返回值和错误

waitpid的返回值比wait稍微复杂一些,一共有3种情况:

  1. 当正常返回的时候,waitpid返回收集到的子进程的进程ID;
  2. 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
  3. 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;

当pid所指示的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid就会出错返回,这时errno被设置为ECHILD;

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include<stdio.h>
#include<stdlib.h>
int main()
{
	pid_t pc, pr;
	pc = fork();

	if (pc < 0) /* 如果fork出错 */
	{
		printf("Error occured on forking.\n");
	}

	else if (pc == 0) /* 如果是子进程 */
	{
		sleep(10); /* 睡眠10秒 */
		exit(0);
	}

	/* 如果是父进程 */
	do {
		pr = waitpid(pc, NULL, WNOHANG); /* 使用了WNOHANG参数,waitpid不会在这里等待 */
		if (pr == 0) /* 如果没有收集到子进程 */
		{
			printf("No child exited\n");
			sleep(1);
		}
	} while (pr == 0); /* 没有收集到子进程,就回去继续尝试 */
	if (pr == pc)
	{
		printf("successfully get child %d\n", pr);
	}
	else
		printf("some error occured\n");

}

父进程经过10次失败的尝试之后,终于收集到了退出的子进程。

因为这只是一个例子程序,不便写得太复杂,所以我们就让父进程和子进程分别睡眠了10秒钟和1秒钟,代表它们分别作了10秒钟和1秒钟的工作。父子进程都有工作要做,父进程利用工作的简短间歇察看子进程的是否退出,如退出就收集它。

提示:可以尝试在最后一个例子中把pr=waitpid(pc, NULL, WNOHANG); 改为pr=waitpid(pc, NULL, 0);或者pr=wait(NULL);看看运行结果有何变化?(修改后的结果使得父进程将自己阻塞,直到有子进程退出为止!)

3.进程的阻塞等待方式

3.1.阻塞等待

  •  如果子进程没有退出,而父进程在进行执行waitpid进行等待,阻塞等待。
  • 大部分IO类的函数例如scanf各种各样的接口,只要涉及IO的或多或少会可能出现阻塞的状态。
  • 现在所用的大部分接口都是阻塞接口(逻辑简单,容易实现)
  •  **阻塞等待(Blocking Wait)**在编程中通常指的是一个线程或进程在等待某个条件满足或某个操作完成之前,会暂停执行其他任务,处于等待状态。这种状态会一直持续,直到等待的条件满足或操作完成,线程或进程才会继续执行后续的任务。在Java中,阻塞等待常用于多线程编程中,用于线程之间的同步和通信。

3.2.进程阻塞:

  1. 把进程的R状态设置为S状态
  2. 把进程的PCB从运行队列移动到等待队列中,不再被调度,而是等待
  3. 本质上是等待某种条件发生。
  • 软件条件满足(子进程退出)
  • 硬件资源就绪(scanf键盘输入数据发生)
#include <sys/wait.h>                                                                                                                  
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
int main()
{
	pid_t pid;
	pid = fork();
	if (pid < 0) {
		printf("%s fork error\n", __FUNCTION__);
		return 1;
	}
	else if (pid == 0) { //child
		printf("子进程已运行, pid is : %d\n", getpid());
		sleep(5);
		exit(257);
	}
	else {
		int status = 0;
		pid_t ret = waitpid(-1, &status, 0);//父进程在这里阻塞式等待,等待5S
		printf("这是等待的测试\n");
		if (WIFEXITED(status) && ret == pid)
		{
			printf("等待子进程5秒成功,子进程返回代码为:%d.\n", WEXITSTATUS(status));
		}
		else {
			printf("等待失败, return.\n");
			return 1;
		}
	}
	return 0;
}

4.进程的非阻塞等待方式: 

在子进程运行期间,父进程除了等待子进程或者是休眠,能不能干点其他的事情❓

        当然可以,在父进程等待,阻塞状态。可以通过设置options来让父进程干点事情。不阻塞等待而是非阻塞等待。

4.1.什么又是非阻塞等待呢?用代码该怎么去实现呢?

        **非阻塞等待(Non-blocking Wait)**则与阻塞等待相反。当线程或进程在等待某个条件满足或某个操作完成时,它不会暂停执行其他任务,而是会继续执行后续的任务。也就是说,即使等待的条件还没有满足或操作还没有完成,线程或进程也不会被阻塞,而是会继续执行其他的操作。

        通过设置options的宏值WNOHANG(wait no hang 等待没有阻塞 = 非阻塞等待)

在计算机中,"HANG" 通常指的是程序或系统出现无响应或停顿的状态,也就是常说的“卡住”或“死机”。

        当程序或系统由于某种原因(如资源锁定、死循环、死锁或外部系统交互问题等)而无法继续正常执行时,就可能会出现"HANG"的情况。这种情况下,用户可能无法与程序或系统进行交互,需要等待程序或系统恢复正常或进行重启操作。

        另外,在一些特定的语境下,"HANG" 也可能被用来描述服务器或数据库的某些服务出现故障或无法访问的情况,这也可以被视为一种"宕机"现象。在这种情况下,"HANG" 指的是服务器或数据库的服务因为某种原因而停止响应或无法提供服务。

具体操作

  1. options这个参数只要一设置就会出现非阻塞等待。
  2. 设置waitpid的WNOHANG本质上是检测一次进程的状态变化。
  3. 调用一次waipid就检测一次。每次调用都是检测,多次调用多次检测。
  4. 非阻塞等待调用多次waitpid,调用waitpid检测是否退出等待过程无问题,只是子进程还未终止,需要等待下次等待。

综上:非阻塞等待的时候 + 循环 = 非阻塞轮询

#include<stdio.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include<stdlib.h>
int main()
{
	pid_t pid;
	pid = fork();
	if (pid < 0) {
		printf("%s fork error\n", __FUNCTION__);
		return 1;

	}
	else if (pid == 0) { //child
		printf("child is run, pid is : %d\n", getpid());
		sleep(5);
		exit(1);

	}
	else {
		int status = 0;
		pid_t ret = 0;
		do
		{
			ret = waitpid(-1, &status, WNOHANG);//非阻塞式等待
			if (ret == 0) {
				printf("child is running\n");
			}
			sleep(1);
		} while (ret == 0);
		if (WIFEXITED(status) && ret == pid) {
			printf("wait child 5s success, child return code is :%d.\n", WEXITS    TATUS(status));
		}
		else {
			printf("wait child failed, return.\n");
			return 1;
		}
	}
	return 0;
}

5.阻塞等待VS非阻塞等待

场景:张三找李四求助帮他复习期末考试。张三在李四的楼下等待李四就绪。

5.1.非阻塞等待:

张三每隔几分钟就给李四打电话询问他是否就绪了,张三在没有打电话的时间看书/游戏/抖音

  1. 就绪的过程本质就是非阻塞等待。
  2. 张三非阻塞等待李四过程 == 函数调用
  3. 张三给李四打电话 == 函数传参
  4. 李四说等着没好 == 函数的返回值
  5. 每次函数调用的本质是检测李四的状态(是否就绪)
  6. 立刻有返回值,多次等待,多次返回。
  • pid_ t waitpid(pid_t pid, int *status, WNOHANG);
  • pit_t == 0 :检测是成功的,只不过子进程还没退出,需要你下一次进行重复等待。
  • pit_t > 0 :等待成功,子进程退出了,并且父进程回收成功。
  • pit_t < 0 :等待失败。

5.2.阻塞等待:

        张三一直给李四打着电话,直到李四就绪,期间张三一直等待李四就绪,不敢别的事情。一直检测李四的状态(不就绪,就不返回)
      一直等待。直到子进程终止才返回。

  1. pid_ t waitpid(pid_t pid, int *status, 0);
  2. pit_t > 0 :等待成功,子进程退出了,并且父进程回收成功。
  3. pit_t < 0 :等待失败。

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

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

相关文章

node配置热更新nodemon

nodemon 安装 全局或者开发依赖都可以 npm install nodemon -g npm install nodemon -D配置 文件配置nodemon.json {"watch": [ // 改动后需要编译成es5的源文件目录和配置等目录以及文件"./src","./conf",".env"],"ext&quo…

QT——设计概述

一、QT的概述 1、QT是什么? Qt是一个跨平台的 C++ 开发库,主要用来开发图形用户界面(Graphical User Interface,GUI)程序,当然也可以开发不带界面的命令行(Command User Interface,CUI)程序。 2、QT可以做什么? Qt 虽然经常被当做一个 GUI 库,用来开发图形界面应…

同三维T80005EH4 H.265 4路高清HDMI编码器

同三维T80005EH4 H.265 4路高清HDMI编码器 4路HDMI输入2路3.5音频输入&#xff0c;第1路和第2路HDMI可支持4K30&#xff0c;其它支持高清1080P60 产品简介&#xff1a; 同三维T80005EH4 4路HDMI高清H.265编码器采用最新高效H.265高清数字视频压缩技术&#xff0c;具备稳定…

【动态规划算法题记录】70. 爬楼梯——递归/动态规划

题目描述 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 题目分析 递归法&#xff08;超出时间限制&#xff09; 递归参数与返回值 void reversal(int i, int k) 每次我们处理第i个台阶到第k个…

具有不确定性感知注意机制的肺结节分割和不确定区域预测| 文献速递-深度学习结合医疗影像疾病诊断与病灶分割

Title 题目 Lung Nodule Segmentation and UncertainRegion Prediction With an Uncertainty-Aware Attention Mechanism 具有不确定性感知注意机制的肺结节分割和不确定区域预测 01 文献速递介绍 肺结节分割在肺癌计算机辅助诊断&#xff08;CAD&#xff09;系统中至关重…

什么是1+N+b模式的费用直投门店管理体系?

在快消行业&#xff0c;为争夺市场份额&#xff0c;企业每年需投入大量资金。在此过程中&#xff0c;您是否也像李总一样面临着如下困惑&#xff1a; 为解决李总所面临的难题&#xff0c;纷享销客秉承“终端为王”的经营理念&#xff0c;创造性地推出了基于1Nb模式的费用直投门…

携手知名律所,合合信息旗下名片全能王打造数字化名片“新范式”

在低碳办公理念盛行的当下&#xff0c;数字名片成为商务交流的新方式&#xff0c;以数字化智能化赋能绿色化。近期&#xff0c;合合信息旗下名片全能王企业数字名片升级&#xff0c;与上海市律师协会、广州市律师协会、北京大成&#xff08;上海&#xff09;律师事务所等多家律…

如何用Xinstall CPS结算系统打破传统营销桎梏,实现用户增长?

在互联网流量红利逐渐衰退的今天&#xff0c;App推广和运营面临着前所未有的挑战。如何快速搭建起满足用户需求的运营体系&#xff0c;成为了众多企业急待解决的问题。而在这个关键时刻&#xff0c;Xinstall CPS结算系统应运而生&#xff0c;以其独特的优势帮助企业解决了一系列…

ESP-IDF OTA升级过程中遇到的“esp_transport_read returned:-1 and errno:128”问题(2)

接前一篇文章:ESP-IDF OTA升级过程中遇到的“esp_transport_read returned:-1 and errno:128”问题(1) 上一回讲了笔者在进行ESP-IDF的OTA调试和测试时遇到了一个新问题:大升级文件下载失败。 在网上寻找解决办法无果后,求助于乐鑫的技术支持,答复说官方例程没有问题。笔…

Django之云存储(一)

一、介绍 用户上传的文件以及项目中使用的静态文件,除了保存在本地服务器,还在可以保存在云服务中,比如: 阿里云七牛云(课程选用)亚马逊云等1.1、使用方式 注册账号 七牛云开发者平台 实名认证 创建空间

【高频电子线路】选频网络概述

什么是选频网络 选频网络是用来“滤波”的电路&#xff0c;用来滤除特定频率的信号以得到目标信号。 选频网络的分类 知识补充 电容和电感的基本性质 在电容中&#xff0c;电流与电压的变化率成正比&#xff0c;电流超前90。 在电感中&#xff0c;电压与电流的变化率成正…

算法:模拟题目练习

目录 题目一&#xff1a;替换所有的问号 题目二&#xff1a;提莫攻击 题目三&#xff1a;N字形变换 题目四&#xff1a;外观数列 题目五&#xff1a;数青蛙 首先先解释一下模拟算法是什么&#xff0c;其实模拟算法就是题目让我们干什么我们就干什么&#xff0c;思路比较简…

kettle从入门到精通 第六十八课 ETL之kettle kettle随机数生成的一些方案

1、在做ETL数据抽取的时候&#xff0c;会用到生成随机数的功能&#xff0c;今天我们一起来学习下如何生成随机数据。如下图所示 2、将生成随机数拉倒画布即可&#xff0c;然后设置字段名称和选择合适的类型&#xff0c;如下图所示&#xff1a; 类型&#xff1a; 随机数字&…

【后端】Java学习笔记(二周目-1)

环境 安装JDK8并配置环境变量 jvm核心类库jre jre开发工具jdk Java基础运行流程&#xff0c;代码从上到下&#xff0c;从左往右&#xff0c;只运行一次 桌面—>新建文件夹—>新建记事本—>打开编写代码 public class Hello{public static void main(String[] arg…

sizeof和strlen

1.sizeof和strlen的对比 1.1sizeof sizeof是计算变量所占内存空间大小的&#xff0c;单位是&#xff1a;字节 如果操作数是类型的话&#xff0c;计算的是使用类型创建的变量所占内存空间的大小。 sizeof只关注占用内存空间的大小&#xff0c;不在乎内存中存放的是什么数据 …

深入理解MySQL分区技术

前言&#xff1a; 在数据量不断增长的当今时代&#xff0c;数据库的性能优化变得尤为重要。MySQL作为一款广泛使用的数据库管理系统&#xff0c;提供了多种性能优化手段&#xff0c;其中分区技术是提升大型表处理效率的有效方法之一。通过将数据分散到多个独立的物理子表中&am…

ArcGIS Pro SDK (三)Addin控件 2 窗格界面类

ArcGIS Pro SDK &#xff08;三&#xff09;Addin控件 2 窗格界面类 目录 ArcGIS Pro SDK &#xff08;三&#xff09;Addin控件 2 窗格界面类15 ArcGIS Pro 后台选项卡15.1 添加控件15.2 Code15.2.1 选项卡按钮15.2.2 选项卡页 16 ArcGIS Pro 窗体16.1 添加控件16.2 Code 17 A…

Bean基础配置

黑马程序员SSM 文章目录 一、Bean基础配置二、bean别名配置2.1 ban的别名配置2.2 注意事项 三、Bean作用范围配置3.1 Bean作用范围3.2 bean作用范围说明 一、Bean基础配置 二、bean别名配置 2.1 ban的别名配置 2.2 注意事项 获取bean无论是通过id还是name获取&#xff0c;如果…

webp2jpg网页在线图片格式转换源码

源码介绍 webp2jpg-免费在线图片格式转化器, 可将jpeg、jpg、png、gif、 webp、svg、ico、bmp文件转化为jpeg、png、webp、webp动画、gif文件。 无需上传文件&#xff0c;本地即可完成转换! 源码特点&#xff1a; 无需上传&#xff0c;使用浏览器自身进行转换批量转换输出we…

汇编语言程序设计之数据传输类指令

文章目录 数据传送类指令通用数据传送指令MOVXCHGXLAT 换码指令 堆栈操作指令标志传送指令标志位操作指令地址传送指令 算术运算类指令常用标志位加法指令ADDADCINC 减法指令SUBSBBDECNEGCMP 乘法指令MUL(无符号&#xff09;IMUL&#xff08;有符号&#xff09;MUL和IMUL对符号…