【Linux】第十八站:进程等待

news2024/12/26 21:40:09

文章目录

  • 一、进程等待的必要性
    • 1.进程等待是什么
    • 2.进程等待的必要性
    • 3.为什么要进程等待呢?
  • 二、进程等待的方法
    • 1.问题
    • 2.wait
    • 3.waitpid
    • 4.status的原理
    • 5.等待失败
    • 6.与status有关的两个宏
    • 7.options

一、进程等待的必要性

1.进程等待是什么

通过系统调用wait/waitpid,来进行对子进程状态检测与回收的功能!

2.进程等待的必要性

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

3.为什么要进程等待呢?

答案已经在前面回答了

就是因为僵尸进程无法被杀死,需要通过进程等待来杀掉它,进而解决内存泄漏问题----必须解决的

我们要通过进程等待,获得子进程的退出情况,要能够知道布置给子进程的任务,它完成的怎么样了----要么关心,也可能不关心

二、进程等待的方法

1.问题

我们使用如下代码

#include<unistd.h>    
#include<stdlib.h>    
#include<stdio.h>
int main()    
{    
    pid_t id = fork();    
    if(id < 0)                                                                
    {                                          
        perror("fork:");    
        return 1;    
    }         
    else if(id == 0)    
    {                     
        int cnt = 5;    
        while(cnt)    
        {                   
            printf("I am child,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt);    
            cnt--;    
            sleep(1);    
        }    
        exit(0);        
    }                 
    else     
    {                                                                               
        while(1)      
        {    
            printf("I am parent,pid:%d,ppid:%d\n",getpid(),getppid());    
            sleep(1);    
        }    
    }    
    return 0;                                                                                                                                                                                  
}

上述代码的功能不难理解。然后我们使用监控去运行一下

while :; do ps ajx | head -1 && ps ajx | grep -v grep |grep waitTest; echo "---------------------------------"; sleep 1;done 

运行结果如下所示

image-20231116145041367

为了解决上面的问题,我们可以让父进程通过调用wait/waitpid进行僵尸进程的回收问题!

2.wait

我们先来看wait函数

image-20231116145824190

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);

返回值:

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

参数:

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

这个系统调用的作用就是等待一个进程,直到它的状态发生改变。

对于wait函数,我们可以看到它需要传入一个指针,但是在wait函数,我们先不关心它的这个参数。也就是我们先给他一个NULL指针

这个wait它会返回一个值,这个值就是对应等待的子进程的pid

#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)                                                                        
    {                                                                   
        int cnt = 5;                                                                  
        while(cnt)                                                                 
        {                                                             
            printf("I am child,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt);           
            cnt--;                                                                 
            sleep(1);                                                                    
        }                                                                      
        exit(0);                                                                            
    }                                                                    
    else                                                                        
    {        
        int cnt = 10;                                                                             
        while(cnt)                                                                  
        {                                                                         
            printf("I am parent,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt);                        
            cnt--;                                                                        
            sleep(1);                                                                         
        }                                                                                          
        pid_t ret = wait(NULL);                                                                         
        if(ret == id)                                                                                  
        {                                                                                        
            printf("wait success:,ret : %d\n",ret);                        
        }                                                                      
        sleep(5);                                                                                         
    }                                                                                                                                                      
    return 0;                                                
} 

最终它的运行结果为

程序一共经历了三个5秒。第一个五秒钟,是父子进程都存在,第二个五秒,子进程进入僵尸状态。第三个五秒,子进程的僵尸状态被回收了。

image-20231116151813720

所以说,到目前为止,进程等待是必须的。因为这个进程等待最重要的作用就是回收僵尸进程

那么如果我们这个程序有很多个子进程呢?应该等待哪一个呢?其实wait是等待任意一个子进程退出。

所以如果我们要等待多个进程退出,我们要这样做

如下代码所示

#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
    
#define N 10    
    
void RunChild()    
{    
    int cnt = 5;    
    while(cnt)    
    {    
        printf("I am a child process, pid:%d, ppid:%d\n",getpid(),getppid());    
        sleep(1);    
        cnt--;    
    }    
}    
int main()    
{    
    for(int i = 0; i < N; i++)    
    {    
        pid_t id = fork();    
        if(id == 0)    
        {    
            RunChild();    
            exit(0);    
        }    
        printf("create child process: %d success\n",id);    
    }    
    
    sleep(10);    
    
    for(int i = 0; i < N; i++)    
    {    
        pid_t id = wait(NULL);    
        if(id > 0)    
        {    
            printf("wait %d success\n",id);                                                  
        }    
    }    
    sleep(5);
    return 0;    
}

运行结果如下

image-20231116162422040

image-20231116162355469

上面都是子进程会退出的情况,那么如果子进程都不退出呢?

即我们将上面的子进程都改为死循环

那么最终的运行结果是一个也不退出,父进程也不退出,在那里阻塞等待。

image-20231116163402097

所以这说明,如果子进程不退出,父进程默认在wait的时候,调用这个系统调用的时候,也就不返回,默认就叫做阻塞状态!

所以说阻塞不仅仅只是像我们之前要等待scanf的时候,需要等待硬件,还有可能等待软件

3.waitpid

现在我们已经知道对于子进程的僵尸进程问题是如何解决的了,就是使用wait即可。这解决了进程等待中最为重要的一点,那么如果父进程需要获得子进程的退出时的状态,得知子进程任务完成的怎么样了,那么应该如何解决呢?

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

返回值:

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

参数:

  • pid:

Pid=-1,等待任一个子进程。与wait等效。

Pid>0.等待其进程ID与pid相等的子进程。

  • status:

WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)

WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

  • options:
    WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。

从这里的描述,我们就可以知道,wait其实就相当于waitpid的一个子集

即在这段代码中,wait与waitpid是等价的

image-20231116170410856

image-20231116170457366

在我们这个wait和waitpid的函数中,他们都有一个status,这个就是用来获取退出时的状态的。如果我们不想用,直接设置为空即可,这两个函数的这个参数是一样的

image-20231116170713686

对于这个status,它是一个输出型参数。其次,这个int是被当作几部分来使用的。

我们可以先来使用一下,为了展示出效果,我们让子进程的退出设置为1

image-20231116172438286

最终运行结果为

image-20231116172521576

我们可以发现,我们退出信息本应该是1,但是结果却是256

这里我们就有如下几个问题

  1. 子进程退出,一共会有几种退出场景呢?

对于这个问题,我们在前面已经知道了:总共三种场景

①:代码运行完毕,结果正确 ②:代码运行完毕,结果不正确 ③:代码异常终止

  1. 父进程等待,期望获得子进程退出的哪些信息呢?

①:子进程代码是否异常?②:没有异常的话,结果对吗?这里可以用exitcode退出码来获取,不对是什么原因呢?可以用不同的退出码表示原因

所以我们可以看到,这个status需要做的事情其实挺多的,所以这个变量注定了不能被看作一个,而是要划分为几个部分

image-20231116173351873

对于这个status它一共有32位,但是我们目前只考虑它的低16位

低八位,用于表示是否出异常了。其中有一共比特位是core dump标志位,我们后面再谈

我们在前面说过,一共进程异常了,本质就是该进程收到了一个信号。

对于它的次低八位,代表的就是退出的状态,即退出码

image-20231116180710223

比如我们刚刚所说的明明是退出码是1,但是打印结果是256,其实就是退出码的最低位被置为了1

image-20231116180806236

对于第七位,我么可以看到总共有64种信号,但是我们会发现没有0号信号,是因为0要代表正常终止。所以总共65种状态,就需要七位来表示。

image-20231116180943671

那么在这里我们可能会有一个问题,就是我们觉得可能没有必要这样做,因为完全可以设置一个全局变量,然后再子进程退出的时候,修改这个全局变量来处理状态就可以了,不需要用wait来处理?

其实这是因为,进程具有独立性

即便子进程中将这个全局变量给修改了,但是父进程也是看不到的。所以必须得通过wait系统调用,让操作系统去拿这个数据。

我们可以这样做,就可以分别拿出两种码了

image-20231116183217652

运行结果为

image-20231116183253354

如果我们的退出码是11

image-20231116183415758

那么运行结果的退出码就是11

image-20231116183449280

我们也可以模拟当他出现异常的时候

image-20231116183853947

可以看到,子进程第一次就发生了除0错误,直接进入了僵尸状态。

并且最终就是8号信号浮点数错误

image-20231116184002668

我们也可以让他进入死循环,然后我们使用kill去杀掉这个信号

image-20231116184951852

4.status的原理

在下图种,意思是,父进程再某行种调用了waitpid这个函数,cpu去调度父进程,当子进程执行完毕的时候。子进程为了保证自己的结果可以被上层获取,子进程可以允许把代码和数据释放掉,但是子进程对应的进程控制块不能被释放掉。我们需要将子进程退出时的信息放入进程控制块中

image-20231116185916779

所以子进程中一定有sigcode,exitcode

如下在linux内核中就可以看到这两个

image-20231116190251450

当他退出时,会将值写入exit_code中,当他异常终止时,会将信号写入exit_signal中。然后waitpid就可以读取这里面的数据了

waitpid一方面可以检测当前进程是否退出了,比如说z状态。是z状态,就直接读取这两个值,通过位运算,让上层拿到

image-20231116190447768

所以waitpid的核心工作就是读取子进程的task_struct,内核数据结构对象,并且将进程的Z状态改为X状态

那么为什么不让我们写代码时候直接访问子进程的pcb中呢,而是必须要通过系统调用呢?

我们必须要通过管理者拿到被管理者的数据,不能直接拿被管理者的数据,因为操作系统不相信任何人

5.等待失败

前面我们知道,wait/waitpid函数在等待失败的时候,会返回-1,那么什么时候会失败呢?

这里有一个很经典的场景,那就是等待的进程不存在或者等待的进程不是该进程的子进程

image-20231116201118094

image-20231116201149355

这就说明了,等待的进程必须是该进程的子进程

6.与status有关的两个宏

其实在我们的代码中,如果要让我们去写这两个的话是比较麻烦的

image-20231116201653437

所以linux提供了两个宏

  • WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
  • WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

我们可以这样用

image-20231116202146635

image-20231116202211322

我们可以在试一下多进程的

image-20231116203830467

运行结果为

image-20231116203932399

不过要注意的是,我们这个具有一定的偶然性,因为多进程的话不一定越早的就一定是最快的。取决于CPU的调度


所以现在我们就知道了前面所说的进程等待的原因之二了:我们要通过进程等待,获得子进程的退出情况,要能够知道布置给子进程的任务,它完成的怎么样了----要么关心,也可能不关心。我们也就知道了main函数的返回到底是什么了

就好比,当我们正在运行我们上面的代码的时候,bash也在等待这个进程退出。因为这个进程也是bash的子进程。

7.options

我们知道,wait本身会等待子进程,但是当子进程一直停不下来的时候,父进程只能等待,wait会导致父进程阻塞调用,导致父进程陷入阻塞状态。options可以指定等待方式。如果是0,则是阻塞等待方式(即父进程要等待子进程,子进程一直处于R,父进程就一直处于S,然后将父进程投入到等待队列中,投入到了子进程的等待队列。当子进程结束的时候,再从这个等待队列中将父进程唤醒)

我们在等待的时候,还可以选择另外一种等待方式:非阻塞轮询

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

在这个函数中,如果options是0,那么就是阻塞等待方式

还有一种选项是WNOHANG (wait no hang, hang可以理解为夯住了,类似于服务器宕机了,意思是等待的时候不要夯住,也就是非阻塞)

类似于小明有事找小王

如果小明在楼下给小王隔一会就打一下电话,因为小王一直说忙着等会马上到。这就是非阻塞轮询(小明是在不打电话的时候是非阻塞的,而且是一直循环的打电话)

如果小明在楼下给小王打电话,然后不挂了,知道小明事情做完才挂电话,这就是阻塞等待(因为小明啥也干不了了)

如果小明在楼下给小王隔一会就打一下电话,在这中间的间隙做一些自己的事情,就是非阻塞轮询+做自己的事情

与之对应的就是pid_t的返回值其实应该有三种

大于0:等待成功,即返回子进程的pid

小于0:等待失败

等于0:等待的条件还没有就绪。

如下就是非阻塞轮询的示例

image-20231116212255400

运行结果如下所示:

注意在非阻塞轮询中,最好加上sleep,否则的话频繁的printf,可能会对系统压力比较大,导致卡住了。达不到预期的结果。


那么这里我们可能会疑惑父进程具体要具体做什么样子的工作?

我们可以用下面这个例子

#include<stdio.h>    
#include<unistd.h>    
#include<stdlib.h>    
#include<sys/types.h>    
#include<sys/wait.h>    
       
#define TASK_NUM 10    
typedef void(*task_t)();    
task_t tasks[TASK_NUM];    
void task1()    
{    
    printf("这是一个执行打印日志的任务,pid:%d\n",getpid());    
}    
void task2()    
{    
    printf("这是一个执行检测网络健康状况的任务,pid:%d\n",getpid());    
}    
void task3()    
{    
    printf("这是一个绘制图形界面的任务,pid:%d\n",getpid());    
}    
void InitTask()    
{    
    for(int i = 0; i < TASK_NUM; i++) tasks[i] = NULL;    
}    
int AddTask(task_t t)    
{    
    int pos = 0;    
    for(pos = 0; pos < TASK_NUM; pos++)    
    {    
        if(!tasks[pos]) break;    
    }    
    if(pos == TASK_NUM) return -1;                                                                                                             
    tasks[pos] = t;    
    return 0;    
}    
void DelTask()                                     
{}
void CheckTask()
{}
void UpdateTask()
{}
void ExecuteTask()
{
    for(int i = 0; i < TASK_NUM; i++)
    {
        if(!tasks[i]) continue;
        tasks[i]();
    }
}
int main()
{
    pid_t id = fork();
    if(id < 0)                                     
    {
        perror("fork:");
        return 1;
    }
    else if(id == 0)
    {
        int cnt = 5;
        while(cnt)
        {
            printf("I am child,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt);
            cnt--;
            sleep(1);
        }
        exit(11);
    }
    else
    {
        int status = 0;
        InitTask();
        AddTask(task1);
        AddTask(task2);
        AddTask(task3);
        while(1) //轮询
        {
             pid_t ret = waitpid(-1, &status,WNOHANG); //非阻塞
             if(ret > 0)
             {
                 if(WIFEXITED(status))
                 {
                     printf("进程是正常跑完的,退出码为:%d\n",WEXITSTATUS(status));
                 }
                 else 
                 {
                     printf("进程出异常了\n");
                 }
                 break;
             }
             else if(ret < 0)
             {
                 printf("wait fail\n");
                 break;
             }
             else 
             {
                 ExecuteTask();
                 usleep(500000);
                 //printf("子进程还没有退出,在等等\n");
                 //sleep(1);
             }
        }
        sleep(3);
    }
    return 0;
}

运行结果为

image-20231117202347179

在这里我们需要注意的是,在这里,我们等待子进程才是最核心的任务,这些其他的任务都是一些顺带的事情。

这些顺带的任务不可以太重了,应该得是轻量化的任务。比如打印日志,检测网络状况等。而且在这里,我们这里也只是延迟回收了一会子进程,而不是说不回收子进程了。

在上面的代码中,我们只是单进程的等待任务,如果我们想要改为多进程的等待任务的话,那么我们只需要将这里稍作修改即可,不让他直接break,而是设置一个计数器,计数子进程的个数,当一个子进程结束后,计数器减减即可。只有减到0以后,才是break。还有就是在出错的时候,也break即可

image-20231117203625030

最后一点需要注意的是

waitpid在回收子进程的时候,它可以保证父进程一定是最后一个被回收的。因为子进程可以全部被waitpid给回收掉。

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

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

相关文章

压缩感知学习

对稀疏和稀疏矩阵的认识 采样率80Mhz 采样间隔12.5ns,样本数量为800个 一帧时长800*12.5ns 10us 频域间隔 1/10us 0.1Mhz 第一个点的频率是0 第21个点的频率是2Mhz 在只考虑正半轴&#xff0c;也即400个点的情况下&#xff0c;分别让不同的频点取1 &#xff0c;然后对其进…

MySQL优化的底层逻辑

文章目录 前言索引的底层结构数据与索引的关系聚簇索引的数据存储普通索引的数据存储 索引的命中逻辑怎么理解索引失效总结 前言 去年刚开始写博客的时候写了一篇《MySQL性能调优参考》&#xff0c;文章中提到优化的几个技巧&#xff0c;比如数据类型的使用、范式和反范式的合…

安装部署Esxi7.0并创建虚拟机

1. Esxi介绍 ESXI虚拟平台是VMware出品的一个强大平台&#xff0c;它可以直接安装在物理机上&#xff0c;从而充分利用物理奖性能&#xff0c;虚拟多个系统出来。ESXI是一个带WEB管理后台的软件&#xff0c;非常适合安装在服务器上&#xff0c;然后直接通过网页进行远程管理。…

测试开发环境下centos7.9下安装docker的minio

按照以下方法进行 1、安装docker&#xff0c;要是生产等还是要按照docker-ce yum install docker 2、启动docker service docker start 3、 查看docker信息 docker info 4、加到启动里 systemctl enable docker.service 5、开始docker pull minio/minio 但报错&#x…

TP-LINK联洲面试题

文章目录 1.说一下微服务架构?2.微服务优缺点3.负载均衡的实现算法4.Redis集群部署方式?5.MySQL主从复制?5.1 配置流程5.2 优缺点分析6.口头手撕快排7.队列实现栈和栈实现队列7.1 队列实现栈7.2 栈实现队列8.进程有几种状态?9.Spring Boot Actuator?10.外键、主键和索引?…

ImportError: cannot import name ‘url_quote‘ from...

&#x1f468;&#x1f3fb;‍&#x1f4bb; 热爱摄影的程序员 &#x1f468;&#x1f3fb;‍&#x1f3a8; 喜欢编码的设计师 &#x1f9d5;&#x1f3fb; 擅长设计的剪辑师 &#x1f9d1;&#x1f3fb;‍&#x1f3eb; 一位高冷无情的编码爱好者 大家好&#xff0c;我是全栈工…

Netty传输object并解决粘包拆包问题

⭐️ 前言 大家好&#xff0c;笔者之前写过一篇文章&#xff0c;《Netty中粘包拆包问题解决探讨》&#xff0c;就Netty粘包拆包问题及其解决方案进行了探讨&#xff0c;本文算是这篇博客的延续。探讨netty传输object的问题。 本文将netty结合java序列化来传输object并解决粘包…

C语言——求1/1-1/2+1/3-......+1/99-1/100的值

#include<stdio.h> int main() {int i 1;double sum 0;int flage 1;for(i 1;i < 100; i){sumflage*1.0/i;flage -flage; //正负号}printf("%lf\n",sum);return 0; }

2024年山东省职业院校技能大赛中职组“网络安全”赛项竞赛试题-B

2024年山东省职业院校技能大赛中职组 “网络安全”赛项竞赛试题-B 一、竞赛时间 总计&#xff1a;360分钟 二、竞赛阶段 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 A、B模块 A-1 登录安全加固 180分钟 200分 A-2 本地安全策略设置 A-3 流量完整性保护 A-4 …

二十一、数组(1)

本章概要 数组特性 用于显示数组的实用程序 一等对象返回数组 简单来看&#xff0c;数组需要你去创建和初始化&#xff0c;你可以通过下标对数组元素进行访问&#xff0c;数组的大小不会改变。大多数时候你只需要知道这些&#xff0c;但有时候你必须在数组上进行更复杂的操作…

Ubuntu18.04安装Moveit框架

简介 Moveit是一个由一系列移动操作的功能包组成的集成化开发平台,提供友好的GUI,是目前ROS社区中使用度排名前三的功能包,Moveit包含以下三大核心功能,并集成了大量的优秀算法接口: 运动学:KDL,Trac-IK,IKFast...路径规划:OMPL,CHMOP,SBPL..碰撞检测:FCL,PCD... 一、更新功…

前段-用面向对象的方式开发一个水管小鸟的游戏

首先准备好各类空文件 index.js css html 和图片 图片是下面这些&#xff0c;如果没有的可在这里下载 2 开发开始 好了&#xff0c;基础准备工作完毕&#xff0c;开发开始&#xff0c; 首先&#xff0c;先把天空&#xff0c;大地&#xff0c;小鸟的盒子准备好&#xff0c;并…

基于探路者算法优化概率神经网络PNN的分类预测 - 附代码

基于探路者算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于探路者算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于探路者优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络…

main函数的数组参数是干嘛用的

今天在看项目代码的时候&#xff0c;突然看到项目中用到了main函数的参数args&#xff0c;在这之前我还没怎么注意过这个参数&#xff0c;一时间居然不知道这个参数是干嘛的&#xff01; 虽然也写过一些java和scala&#xff0c;但是确实没遇到过会用这个参数的情况。 网上就查…

【PIE-Engine 数据资源】中国叶面积指数(LAI)月度合成产品

文章目录 一、 简介二、描述三、波段四、示例代码运行结果参考资料 一、 简介 数据名称中国叶面积指数&#xff08;LAI&#xff09;月度合成产品时间范围2002-2021年空间范围全国数据来源航天宏图代码片段var images pie.ImageCollection(“EMDO/MODIS_MONTH_LAI_CHINA”) 二…

Arduino项目式编程教学前言

前言–先聊聊我的经历 在停更数年之后&#xff0c;还是打算重新开启Arduino编程教学这一项目&#xff1b;这几年间&#xff0c;我从Arduino编程开发教学&#xff0c;转到C及python教学&#xff0c;又到如今的高中数学教学&#xff0c;跨度竟如此之大&#xff0c;但始终未脱离教…

echarts 三角锥形柱状图 + 带阴影的折线图示例

该示例有如下几个特点&#xff1a; ①三角锥形折线图 ②折线图自带阴影 ③三角锥形鼠标放置时颜色改变 ④数据随着鼠标移动而展示 ⑤鼠标放置时tooltip样式自定义&#xff08;echarts 实现tooltip提示框样式自定义-CSDN博客&#xff09; 代码如下&#xff1a; this.options …

【GUI】-- 08 JButton、JRadioButton、JCheckBox

GUI编程 03 Swing 3.5 JButton 图片置于按钮之上的JButton&#xff1a; package com.duo.lesson05;import javax.swing.*; import java.awt.*; import java.net.URL;public class JButtonDemo01 extends JFrame {public JButtonDemo01() {Container contentPane getConten…

opencv(4):颜色空间

文章目录 颜色空间RGB 人眼的色彩空间HSV 色彩空间HSLYUVYUV420&#xff1a;YUV422&#xff1a;YUV444&#xff1a; 颜色空间转换代码示例 颜色空间 不同色彩空间显示效果是不一样的。 RGB 人眼的色彩空间 HSV 色彩空间 HSV 代表色相&#xff08;Hue&#xff09;、饱和度&a…

YOLO改进系列之注意力机制(EffectiveSE模型介绍)

模型结构 ESE(Effective Squeeze and Extraction) layer是CenterMask模型中的一个block&#xff0c;基于SE&#xff08;Squeeze and Extraction&#xff09;改进得到。与SE的区别在于&#xff0c;ESE block只有一个fc层&#xff0c;(CenterMask : Real-Time Anchor-Free Insta…