嵌入式学习 D32:系统编程--进程间通信IPC

article/2025/6/10 13:15:41
  • 引言--进程间通信
  • 管道的概念
  • 管道相关操作
  • 有名管道及其相关操作
  • 信号通信

一、引言--进程间通信

        1)因为空间是独立和隔绝的,数据发不过去,需要进程间的通信来交互,所以需要通信。

        2)linux进程间通信的常用几种方式:

1、古老的通信方式
        无名管道  有名管道  信号

2、IPC对象通信 system v    BSD     suse fedora   kernel.org
        消息队列(用的相对少,这里不讨论)
        共享内存
        信号量集    

3、socket通信
        网络通信(不同主机间交互)

二、管道的概念

        1)    无名管道 ===》pipe ==》只能给有亲缘关系进程通信
                    有名管道 ===》fifo ==》可以给任意单机进程通信

        2)管道的特性:

1、管道是 半双工的工作模式
2、所有的管道都是特殊的文件不支持定位操作。不支持lseek->> fd  fseek ->>FILE* 
3、管道是特殊文件,读写使用文件IO。

其中具有缓冲区,可以考虑,如果是字符串的话,使用fgets,fread,fgetc,
最好使用:open,read,write,close;

        读写时机不同就会触发下面四个特性:

        1)读端存在,一直向管道中去写,超过64k,读端来不及取出,写会阻塞。(阻塞是设备带来的特性,不由函数决定)

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>int  main (int argc ,char **argv)
{int fd[2] = {0};int ret = pipe(fd);if (ret == -1){perror("pipe");return 1;}pid_t pid = fork();if(pid > 0){// fd[0] read   fd[1] writeclose (fd[0]);  // close readsleep(3);       //shi qi du zu se ,shu jv jia gong xu yao shi jian write(fd[1],"hello",6);close(fd[1]);}else if( 0 == pid){close (fd[1]);char buf[10] = {0};read (fd[0],buf,sizeof(buf));printf("father:%s\n",buf);close(fd[0]);}else{perror("fork");return 1;}system("pause");return 0;}

        2)写端是存在的,读管道,如果管道为空的话,读会阻塞。(读阻塞)

ps:即写的慢读的快。此时应该等一会。实际中系统读阻塞触发的次数居多,因为资源总数稀缺的。

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>int  main (int argc ,char **argv)
{int fd[2] = {0};int ret = pipe(fd);if (ret == -1){perror("pipe");return 1;}pid_t pid = fork();if(pid > 0){// fd[0] read   fd[1] writeclose (fd[0]);  // close readchar buf[1024] = {0};memset(buf,'a',sizeof(buf));int i = 0;for(i = 0 ; i < 65 ; i++){write (fd[1],buf , 1024);printf("%d\n", i);}close(fd[1]);}else if( 0 == pid){close (fd[1]);char buf[10] = {0};while(1){sleep(1);}close(fd[0]);}else{perror("fork");return 1;}system("pause");return 0;}

        3)读端的文件描述符关闭,读端关闭,只要调用写的相关函数,就会管道破裂(类似于段崩塌)导致写端结束,即进程结束。(可以用此特性来关闭进程)

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>int  main (int argc ,char **argv)
{int fd[2] = {0};int ret = pipe(fd);if (ret == -1){perror("pipe");return 1;}pid_t pid = fork();if(pid > 0){// fd[0] read   fd[1] writeclose(fd[0]);  //close read end sleep(3);write(fd[1],"hello",5);  //触发管道破裂 gdb 观察printf("aaaa\n");close(fd[1]);}else if( 0 == pid){close (fd[1]);close(fd[0]);}else{perror("fork");return 1;}system("pause");return 0;}

         触发管道破裂用gdb观察,gdb只能跟一个进程走,默认跟父走,若要跟子走,应该在fork之前输入下述命令:

set follow-fork-mode child

        4)双方在通信过程中,写端先关闭,若果管道当中没有数据,那么将会读到0,read返回值为0,说明文件读到结尾。

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>int  main (int argc ,char **argv)
{int fd[2] = {0};int ret = pipe(fd);if (ret == -1){perror("pipe");return 1;}pid_t pid = fork();if(pid > 0){// fd[0] read   fd[1] writeclose (fd[0]);  // close readwrite(fd[1],"hello",5);close(fd[1]);exit(0);}else if( 0 == pid){close (fd[1]);sleep(3);while(1){char buf[10] = {0};int ret = read(fd[0],buf,sizeof(buf));if(0 == ret){printf("read 0\n");break;}printf("father :%s\n",buf);}close(fd[0]);}else{perror("fork");return 1;}system("pause");return 0;}

三、无名管道相关操作

创建并打开管道-->读写管道-->关闭管道(看成特殊的文件操作)

    无名管道 ===》管道的特例 ===>pipe函数
    特性:
    1)亲缘关系进程使用
    2) 有固定的读写端

创建并打开管道:pipe函数

 int  pipe(int pipefd【2】)

参数:pipefd[0] ==>无名管道的固定读端
           pipefd[1] ==>无名管道的固定写端
返回值:成功 0;失败 -1;

注:无名管道的创建应在fork之前完成。

读写管道:read();write();

与文件i/o的读写操作一致。

关闭管道:close()

代码示例:通过pipe实现图片复制

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>int  main (int argc ,char **argv)
{int fd[2] = {0};int ret = pipe(fd);if (ret == -1){perror("pipe");return 1;}pid_t pid = fork();if(pid > 0) // readwenjian write guandao{// fd[0] read   fd[1] writeclose (fd[0]);  // close writeint fd1 = open(argv[1],O_RDONLY);if(-1 == fd1){perror("open");exit(1);}while(1){char buf [58358] = {0};int ret = read (fd1,buf,sizeof(buf));if(0 == ret){break;}write(fd[1],buf,ret);}close(fd[1]);close(fd1);}else if( 0 == pid) //qu guan dao xie wen jian{close (fd[1]);int fd2 = open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0666);if( -1 == fd2){perror("child open");return 1;}while(1){char buf[58358] = {0};int ret = read(fd[0],buf,sizeof(buf));if(0 == ret){break;}write(fd2,buf,ret);}close(fd[0]);close(fd2);}else{perror("fork");return 1;}// system("pause");return 0;}

相关问题:

 1、父子进程是否都有fd[0] fd[1],如果在单一进程中写fd[1]能否直接从fd[0]中读到?

         可以,写fd[1]可以从fd[0]读。

2、管道的数据存储方式是什么样的,数据是否一直保留?
    栈, 先进后出
   队列形式存储 读数据会剪切取走数据不会保留
   先进先出

3、管道的数据容量是多少,有没有上限值。
    操作系统的建议值: 512* 8 = 4k
    代码测试实际值:   65536byte= 64k

4、管道的同步效果如何验证?读写同步验证。
    读端关闭能不能写? 不可以 ===>SIGPIPE 异常终止 
    写端关闭能不能读? 可以,取决于pipe有没有内容,===>read返回值为0 不阻塞

    结论:读写端必须同时存在,才能进行管道的读写。
5、固定的读写端是否就不能互换?能否写fd[0] 能否读fd[1]?  

        不可以,是固定读写端。

四、有名管道及其相关操作

        本机上的任意进程的通信,向fifo中写,读从fifo中取。

创建有名管道-->打开有名管道-->读写管道-->关闭管道-->卸载有名管道(读端)

  • 1)remove(文件名)为卸载命令,不删文件的话会一直存在,下次重新开机系统就会在里面开64k空间用于通信。
  • 2)有名管道使用时当调用open时会阻塞,等对应的另一端出现再开始运行,只有打开有名管道时会出现;

  • 创建有名管道:mkfifo函数(在指定的pathname路径+名称下创建一个权限为mode的有名管道文件)

  • int mkfifo(const char *pathname, mode_t mode);

    参数:pathname要创建的有名管道路径+名称
          mode  8进制文件权限。权限一般0666
    返回值:成功 0, 失败  -1;

    打开有名管道 open

  • 一般只有如下方式:
        int fd-read = open("./fifo",O_RDONLY); ==>fd 是固定读端
        int fd-write = open("./fifo",O_WRONLY); ==>fd 是固定写端
        不能是 O_RDWR 方式打开文件。
        不能有 O_CREAT 选项,因为创建管道有指定的mkfifo函数

  • 注意:该函数使用的时候要注意打开方式, 因为管道是半双工模式,所有打开方式直接决定当前进程的读写方式。

  • 有名管道的读写:文件i/o

  •     读: read(fd-read,buff,sizeof(buff));
        写: write(fd-write,buff,sizeof(buff));

    关闭管道:close();

  • 卸载管道:remove();(将指定的pathname管道文件卸载,同时从文件系统中删除)

  • int unlink(const char *pathname);

    参数: ptahtname 要卸载的有名管道 
            返回值:成功 0,失败  -1; 

    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <strings.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <errno.h>
    int main (int argc, char **argv)
    {int ret = mkfifo("fifo",0666);if( -1 == ret){if(EEXIST != errno){perror("mkfifo");return 1;}}int fd = open("fifo",O_WRONLY);if( -1 == fd){perror("open fifo");return 1;}int srcfd = open(argv[1],O_RDONLY);if(-1 == srcfd){perror("open");exit(1);}while(1){char buf [58358] = {0};int ret = read (srcfd,buf,sizeof(buf));if(0 == ret){break;}write(fd,buf,ret);}// write(fd,"hello",5);close(fd);close(srcfd);// system("pause");return 0;
    }
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <strings.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <errno.h>
    int main (int argc, char **argv)
    {int ret = mkfifo("fifo",0666);if( -1 == ret){if(EEXIST != errno){perror("mkfifo");return 1;}}int fd = open("fifo",O_RDONLY);if( -1 == fd){perror("open fifo");return 1;}int dstfd = open(argv[1],O_WRONLY|O_CREAT|O_TRUNC,0666);if( -1 ==  dstfd){perror(" dstfd open");return 1;}while(1){char buf[58358] = {0};int ret = read(fd,buf,sizeof(buf));if(0 == ret){break;}write( dstfd,buf,ret);}// char buf[10] = {0};// read(fd,buf,sizeof(buf));// printf("w:%s\n",buf);close(fd);close(dstfd);remove("fifo");// system("pause");return 0;
    }

    有名管道 相关问题:

  •     1、是否需要同步,以及同步的位置。
            读端关闭 是否可以写,不能写什么原因。
            写端关闭 是否可以读。

            结论:有名管道执行过程过必须有读写端同时存在。
                  如果有一端没有打开,则默认在open函数部分阻塞。

        2、有名管道是否能在fork之后的亲缘关系进程中使用。
            结论: 可以在有亲缘关系的进程间使用。
            注意: 启动的次序可能会导致其中一个稍有阻塞。

        3、能否手工操作有名管道实现数据的传送。
            读: cat  fifoname
            写: echo "asdfasdf" > fifoname

五、信号通信

        应用:异步通信。 中断..
    1~64;32应用编程。

//关闭

         Term   Default action is to terminate the process.

//忽略

       Ign    Default action is to ignore the signal.
       wait
// 关闭,并保存关键点信息      

       Core   Default action is to  terminate  the  process  and  dump  core  (see
              core(5)).
        gdb a.out -c core

//暂停
       Stop   Default action is to stop the process.

//继续

       Cont   Default  action  is  to  continue  the  process  if  it is currently  stopped.

1)信号kill -l ==>前32个有具体含义的信号

2)kill  -xx  xxxx 

发送进程  信号    接收进程

kill -9 1000
    a.out  9 1000         发送端:

#include <sys/types.h>

#include <signal.h>

int kill(pid_t pid, int sig);  //通过该函数可以给pid进程发送信号为sig的系统信号。

参数:pid 要接收信号的进程pid
          sig 当前程序要发送的信号编号 《=== kill  -l
    返回值:成功 0; 失败  -1;

#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/types.h>
#include <unistd.h>int main (int argc ,char **argv)
{if(argc < 3){printf("usage: ./a.out pid signum\n");return 1;}pid_t pid = atoi(argv[1]);int num = atoi(argv[2]);int ret = kill(pid,num);if(-1 == ret){perror("kill");return 1;}system("pause");return 0;
}

3) (与kill相似)给进程自己发送sig信号

int raise(int sig)== kill(getpid(),int sig);

4)SIGALAM:定时由系统给当前进程发送信号,也称为闹钟函数。

闹钟只有一个,定时只有一次有效,但是必须根据代码逻辑是否执行判断。

#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/types.h>
#include <unistd.h>
int main ( int argc, char **argv)
{alarm(5);while(1){printf("sleep...\n");sleep(1);}system("pause");return 0;
}

  5)pause:进程暂停,不再继续执行,除非 收到其他信号。

int pause(void);

#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/types.h>
#include <unistd.h>
int main ( int argc, char **argv)
{int i = 0;while(1){printf("work ...\n");sleep(1);i++;if(5 == i){pause();}}return 0;
}

 注意:接收端        每个进程都会对信号作出默认响应,但不是唯一响应。
        一般如下三种处理方式:
        1、默认处理
        2、忽略处理 9,19
        3、自定义处理 9,19 捕获 

6)信号注册函数signal原型:(回调函数)

void ( *signal(int signum, void (*handler)(int)) ) (int);

 typedef void (*sighandler_t)(int);
     ===》void (*xx)(int); == void fun(int);
     ===》xx是 void fun(int) 类型函数的函数指针
     ===》typedef void(*xx)(int)   sighandler_t; ///错误
          typedef int   myint;

     ===>sighandler_t signal(int signum, sighandler_t handler);
     ===> signal(int sig, sighandler_t fun);
     ===> signal(int sig, xxx fun);
     ===>fun 有三个宏表示:SIG_DFL 表示默认处理
                           SIG_IGN 表示忽略处理
                           fun     表示自定义处理

示例1:

#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/types.h>
#include <unistd.h>int flag = 0;
void handle(int num)
{flag = 1;
}
int main ( int argc, char **argv)
{signal(SIGALRM,handle);alarm(5);while(1){if( 0 == flag){printf("sleep...\n");}else{printf("working...\n"); }sleep(1);}system("pause");return 0;
}

示例2:

 

#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/types.h>
#include <unistd.h>void handle(int num)
{}
int main ( int argc, char **argv)
{signal(SIGCONT,handle);int i = 0;while(1){printf("work... pid:%d\n",getpid());sleep(1);i++;if(5 == i ){pause();}}system("pause");return 0;}

示例3:

#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/types.h>
#include <unistd.h>void handle1(int num)
{static int i = 0;printf("老爸叫你...\n");i++;if(i ==3){signal(SIGUSR1,SIG_IGN); //忽略信号}}
void handle2(int num)
{static int i = 0;printf("老妈叫你...\n");i++;if(i ==3){signal(SIGUSR2,SIG_DFL); //回复默认信号}}int	main(int argc, char **argv)
{signal(SIGUSR1,handle1);signal(SIGUSR2,handle2);while(1){printf("i'm playing pid:%d\n",getpid());sleep(1);}system("pause");return 0;
}


http://www.hkcw.cn/article/NOHKSxXciE.shtml

相关文章

黑马Java面试笔记之 消息中间件篇(Kafka)

一. Kafka保证消息不丢失 Kafka如何保证消息不丢失 使用Kafka在消息的收发过程中都会出现消息丢失&#xff0c;Kafka分别给出了解决方案 生产者发送消息到Brocker丢失消息在Brocker中存储丢失消费者从Brocker接收消息丢失 1.1 生产者发送消息到Brocker丢失 设置异步发送 消息…

java的SPI机制

SPI&#xff08;Service Provider Interface&#xff09;是java提供的一种服务发现机制。允许你定义一个接口或抽象类&#xff0c;然后由第三方实现这个接口&#xff0c;并在运行时动态加载这些实现类 核心思想是&#xff1a;面向接口编程&#xff0c;解耦接口与实现 核心组件…

SpringCloud 分布式锁Redisson锁的重入性 高并发 获取锁

介绍 Redisson 的锁支持 可重入性&#xff0c;这意味着同一个线程在获取锁后&#xff0c;如果再次尝试获取该锁&#xff0c;它可以成功地获得锁&#xff0c;而不会被阻塞。 每次一个线程成功获取锁后&#xff0c;它的持有次数会增加。当线程再次获取该锁时&#xff0c;Rediss…

PyTorch--池化层(4)

池化层&#xff08;Pooling Layer&#xff09; 用于降低特征图的空间维度&#xff0c;减少计算量和参数数量&#xff0c;同时保留最重要的特征信息。 池化作用&#xff1a;比如1080p视频——720p 池化层的步长默认是卷积核的大小 ceil 允许有出界部分&#xff1b;floor 不允许…

【自动思考记忆系统】demo (Java版)

背景&#xff1a;看了《人工智能》中的一段文章&#xff0c;于是有了想法。想从另一种观点&#xff08;⭕️&#xff09;出发&#xff0c;尝试编码&#xff0c;告别传统程序员一段代码解决一个问题的方式。下图是文章原文和我的思考涂鸦✍️&#xff0c;于是想写一个自动思考记…

小白的进阶之路系列之十二----人工智能从初步到精通pytorch综合运用的讲解第五部分

在本笔记本中,我们将针对Fashion-MNIST数据集训练LeNet-5的变体。Fashion-MNIST是一组描绘各种服装的图像瓦片,有十个类别标签表明所描绘的服装类型。 # PyTorch model and training necessities import torch import torch.nn as nn import torch.nn.functional as F impor…

pytorch3d+pytorch1.10+MinkowskiEngine安装

1、配置pytorch1.10cuda11.0 pip install torch1.10.1cu111 torchvision0.11.2cu111 torchaudio0.10.1 -f https://download.pytorch.org/whl/cu111/torch_stable.html 2、配置 MinkowskiEngine库 不按下面步骤&#xff0c;出现错误 1、下载MinkowskiEngine0.5.4到本地 2、查看…

ORACLE 缺失 OracleDBConsoleorcl服务导致https://xxx:port/em 不能访问

这个原因是&#xff0c;操作过一下 ORCL的服务配置变更导致的。 再PATH中添加个环境变量&#xff0c;路径如下 管理员权限运行cmd 等待创建完成 大概3分钟 查看服务 点击第一个访问&#xff0c;下图登录后的截图

分布式流处理与消息传递——向量时钟 (Vector Clocks) 算法详解

Java 实现向量时钟 (Vector Clocks) 算法详解 一、向量时钟核心原理 #mermaid-svg-JcZ1GT0r1ZNSy6W7 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-JcZ1GT0r1ZNSy6W7 .error-icon{fill:#552222;}#mermaid-svg-JcZ…

深入浅出:Oracle 数据库 SQL 执行计划查看详解(1)——基础概念与查看方式

背景 在当今的软件开发领域&#xff0c;尽管主流开发模式往往倾向于采用单表模式&#xff0c;力图尽可能地减少表之间的连接操作&#xff0c;以期达到提高数据处理效率、简化应用逻辑等目的。然而&#xff0c;对于那些已经上线运行多年的运维老系统而言&#xff0c;它们内部往…

多模态大语言模型arxiv论文略读(104)

Talk Less, Interact Better: Evaluating In-context Conversational Adaptation in Multimodal LLMs ➡️ 论文标题&#xff1a;Talk Less, Interact Better: Evaluating In-context Conversational Adaptation in Multimodal LLMs ➡️ 论文作者&#xff1a;Yilun Hua, Yoav…

【Oracle】游标

个人主页&#xff1a;Guiat 归属专栏&#xff1a;Oracle 文章目录 1. 游标基础概述1.1 游标的概念与作用1.2 游标的生命周期1.3 游标的分类 2. 显式游标2.1 显式游标的基本语法2.1.1 声明游标2.1.2 带参数的游标 2.2 游标的基本操作2.2.1 完整的游标操作示例 2.3 游标属性2.3.1…

Ethernet/IP转DeviceNet网关:驱动大型矿山自动化升级的核心纽带

在大型矿山自动化系统中&#xff0c;如何高效整合新老设备、打通数据孤岛、实现统一控制&#xff0c;是提升效率与安全的关键挑战。JH-EIP-DVN疆鸿智能EtherNet/IP转DeviceNet网关&#xff0c;正是解决这一难题的核心桥梁&#xff0c;为矿山各环节注入强劲连接力&#xff1a; …

Nginx + Tomcat 负载均衡、动静分离群集

一、 nginx 简介 Nginx 是一款轻量级的高性能 Web 服务器、反向代理服务器及电子邮件&#xff08;IMAP/POP3&#xff09;代理服务器&#xff0c;在 BSD-like 协议下发行。其特点是占有内存少&#xff0c;并发能力强&#xff0c;在同类型的网页服务器中表现优异&#xff0c;常用…

5.Nginx+Tomcat负载均衡群集

Tomcat服务器应用场景&#xff1a;tomcat服务器是一个免费的开放源代码的Web应用服务器&#xff0c;属于轻量级应用服务器&#xff0c;在中小型系统和并发访问用户不是很多的场合下被普遍使用&#xff0c;是开发和调试JSP程序的首选。一般来说&#xff0c;Tomcat虽然和Apache或…

【算法设计与分析】实验——汽车加油问题, 删数问题(算法实现:代码,测试用例,结果分析,算法思路分析,总结)

说明&#xff1a;博主是大学生&#xff0c;有一门课是算法设计与分析&#xff0c;这是博主记录课程实验报告的内容&#xff0c;题目是老师给的&#xff0c;其他内容和代码均为原创&#xff0c;可以参考学习&#xff0c;转载和搬运需评论吱声并注明出处哦。 4-1算法实现题 汽车…

网络爬虫 - App爬虫及代理的使用(十一)

App爬虫及代理的使用 一、App抓包1. App爬虫原理2. reqable的安装与配置1. reqable安装教程2. reqable的配置3. 模拟器的安装与配置1. 夜神模拟器的安装2. 夜神模拟器的配置4. 内联调试及注意事项1. 软件启动顺序2. 开启抓包功能3. reqable面板功能4. 夜神模拟器设置项5. 注意事…

SQLite详细解读

一、SQLite 是什么&#xff1f; SQLite 是一个嵌入式关系型数据库管理系统&#xff08;RDBMS&#xff09;。它不是像 MySQL 或 PostgreSQL 那样的客户端-服务器数据库引擎&#xff0c;而是一个自包含的、无服务器的、零配置的、事务性的 SQL 数据库引擎。 核心特点 嵌入式/库…

线程池详细解析(三)

本章我们来讲一讲线程池的最后一个方法shutdown&#xff0c;这个方法的主要作用就是将线程池进行关闭 shutdown&#xff1a; public void shutdown() {ReentrantLock var1 this.mainLock;var1.lock();try {this.checkShutdownAccess();this.advanceRunState(0);this.interrup…

口碑对比:杭州白塔岭画室和燕壹画室哪个好?

从口碑方面来看&#xff0c;杭州燕壹画室和白塔岭画室各有特点&#xff0c;以下是具体分析&#xff1a; 燕壹画室 教学成果突出&#xff1a; 其前身燕壹设计工作室在2019 - 2023年专注美院校考设计&#xff0c;有一定的教学积淀&#xff0c;2023年转型后第一年攻联考就斩获浙…