[Linux]进程等待

文章目录

  • 3.进程等待
    • 3.1什么是进程等待
    • 3.2为什么要进程等待
    • 3.3如何进行进程等待?
      • 1.wait
      • 2.waitpid
        • 2.1函数的讲解
        • 2.2status的理解
        • 2.3代码理解
    • 3.4学后而思
      • 1.直接用全局变量获取子进程退出码可以吗?如下
      • 2.进程具有独立性 退出码是子进程的数据 父进程是如何拿到退出码的
      • 3.对内存泄露的认识
      • 4.对于上文中提到的系统设置的宏
      • 5.阻塞等待和非阻塞等待
      • 6.status相关宏加入后的简便写法
      • 7.非阻塞式等待的实现

3.进程等待

3.1什么是进程等待

进程等待: 进程的一种状态 父进程等待子进程退出时的一个过程

3.2为什么要进程等待

进程退出时 会关闭所有的文件描述符 释放在内存中的代码和数据 内核数据结构task_struct会暂时保留 里面存放着进程的退出状态以及统计信息等 父进程创建子进程 让子进程来处理事务 父进程需要得知子进程对任务的完成情况即上述的三种情况在这里插入图片描述且需要获取子进程的退出状态 如果子进程先于父进程退出 父进程则无法获取子进程的退出状态 子进程此时就会处于僵尸状态

所以进程等待的两大原因:

  1. 获取子进程的退出状态 避免出现僵尸进程 减少内存泄漏的概率[回收子进程资源]
  2. 获取子进程对任务的完成情况[获取子进程退出信息]

3.3如何进行进程等待?

在这里插入图片描述
在这里插入图片描述

1.wait

在这里插入图片描述

  1. 返回值:
    函数调用成功+目标进程成功改变状态 返回目标进程的pid
    失败返回-1
  2. 参数:
    输出型参数 获取子进程退出状态 不关心则可以设置成为NULL 表示不获取
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int code = 0;

int main()
{
    pid_t id = fork();
    if (id < 0)
    {
        perror("fork");
        exit(1);        //进程终止 结果不正确
    }
    else if (id == 0)
    {
        //子进程
        int cnt = 5;
        while (cnt)
        {
            printf("cnt: %d, 我是子进程, pid: %d, ppid : %d\n", cnt, getpid(), getppid());
            sleep(1);
            cnt--;
        }
        exit(0);     //子进程运行5s后正常退出
    }
    else
    {
        //父进程
        printf("我是父进程, pid: %d, ppid: %d\n", getpid(), getppid());
        sleep(6);
        pid_t ret = wait(NULL); //阻塞式的等待
        if (ret > 0)
        {
            printf("成功等待子进程改变状态, ret = %d\n", ret);
        }
        while (1)
        {
            printf("我是父进程, pid: %d, ppid: %d\n", getpid(), getppid());
            sleep(1);
        }
    }
}

在这里插入图片描述
在这里插入图片描述
当子进程运行5s完退出 父进程还在运行 此时子进程处于僵尸状态 当父进程运行了7s 父进程执行了wait()函数 我们可以理解为 父进程被调度到子进程的后面或阻塞队列 直到子进程从僵尸状态改变状态为终止态 父进程才继续运行 从man手册我们可以查到wait()函数成功等待返回值为被终止掉的子进程的PID 没有成功等待则返回-1

2.waitpid

2.1函数的讲解

waitpid(pid, NULL, 0) == wait(NULL);
在这里插入图片描述

返回值

  1. 正常返回 waitpid返回收集到的子进程的PID
  2. 没有已退出的子进程可收集即所有的子进程都还在运行 返回0 当options = WNOHANG
  3. 函数调用出错 返回-1 errno被设置成相应的值以指示错误原因(错误原因如目标子进程不存在)

pid_t pid:

传参数pid = -1 表示父进程要等待任意一个子进程 ==
传参数pid = Pid > 0 等待PID是Pid的进程

int* status:

  1. 输出型参数,由操作系统填充int status = 0; waitpid(pid, &status, 0); 操作系统会根据子进程PCB中的退出码和退出信号,将子进程的退出信息通过status反馈给父进程
  2. 如果传递NULL,表示不关心子进程的退出状态信息。
  3. 程序运行结果有三种 status并不是按照整数整体来使用的 它是按照比特位的方式 将32个比特位进行划分 此文只讲解低16位
  • 实际上为了更方便使用status 即不再是用位运算 OS提供了宏定义 调用即可
    WIFEXITED(status): wait if exited进程是否正常退出 若为正常终止子进程 返回真
    WEXITSTATUS(status): wait exit status获取进程退出码 若WIFEXITED非零==>即子进程正常退出(代码跑完了 不是被信号杀死的) 提取子进程退出码

int options:

默认为0 表示阻塞式等待
options = 1: 非阻塞式等待

  • 为了不再程序中写一些数字 通常用宏来指示特定含义
    如1就可用WNOHANG来指示 wait no hang 表示父进程进行非阻塞式等待
  1. 子进程都在运行 函数返回0 不进行持续等待
  2. 子进程正常退出 函数返回子进程PID
2.2status的理解

在这里插入图片描述
在这里插入图片描述

  1. 已知可以通过查看status来获取子进程的退出码进而得知子进程的运行结果/退出状态信息
  2. 简单来说 进程终止有两种方式 代码跑完和没跑完 代码跑完无论结果正确都叫正常终止 而如果是没跑完就终止即为异常终止 即程序崩溃或异常退出 (我们之前没学过进程 所以一直说程序崩溃或程序退出 其实正确的说法应为进程异常退出或进程崩溃)
  3. 进程崩溃/异常退出 本质是OS通过发信号的方式杀掉了进程 可以通过查看进程收到的信号编号来得知子进程收到了几号信号 如果进程崩溃/异常退出 退出码就没有意义 所以我们不仅要获取退出码 还要通过查看收到的信号编号是0(正常跑完) 1~31(异常/崩溃) 来判断退出码是否有意义
    在这里插入图片描述

OS都能发哪些信号呢? 1~31重点了解 32/33没有 34-64了解即可

在这里插入图片描述

  1. 父进程调用waitpid()函数之前定义一个int变量 调用waitpid()函数时 把这个变量地址传给waitpid()函数
  2. 这个函数不仅会等待子进程改变状态 还会把子进程的退出码和进程收到的信号编号以上图形式填充给你传过来的变量 即status 通过对status的位操作可以查看退出码和信号编号
  3. 程序异常,可能是内部代码有问题,也可能是外力杀掉 (子进程代码是否跑完是不确定的)
2.3代码理解
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
    pid_t id = fork();
    if (id < 0)
    {
        perror("fork");
        exit(1); 
    }
    else if (id == 0)
    {
        //子进程
        int cnt = 5;
        while (cnt)
        {
            printf("cnt: %d, 我是子进程, pid: %d, ppid : %d\n", cnt, getpid(), getppid());
            sleep(1);
            cnt--;
        }
        exit(15);
    }
    else
    {
        //父进程
        printf("我是父进程, pid: %d, ppid: %d\n", getpid(), getppid());
        int status = 0;
        pid_t ret = waitpid(id, &status, 0); //阻塞式的等待
        if (ret > 0)
        {   
            int signal_number = status & 0x7F;
            int exit_code = (status >> 8) & 0xFF;

            printf("等待子进程改变状态, ret: %d, 子进程收到的信号编号: %d, 子进程退出码: %d\n", ret, signal_number, exit_code);
        }
    }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
信号编号取最低7位: 按位与0000 0000 0000 0000 0000 0000 0111 1111
退出码取次低8位: 按位与0000 0000 0000 0000 0000 0000 1111 1111
跟1按位与: 值不变 是0的是0 是1的是1
跟0按位与: 值全变0

通过可以修改代码 可以看到对应的信号

 子进程死循环 外部 kill -9 子进程pid : 杀死子进程
 signal_number = 9(SIGKILL:signal kill)
 exit_code = 0 sn!=0 退出码无意义
 cnt--;
 
 signal_number = 11(SIGSEGV:signal segmentation violation)
 exit_code = 0 sn!=0 退出码无意义
 int * p = NULL;
 *p = 100;
 
 signal_number = 8(SIGFPE:signal float point error) 
 exit_code = 0 sn!=0 退出码无意义
 int a = 10;
 a /= 0;

3.4学后而思

1.直接用全局变量获取子进程退出码可以吗?如下

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int code = 0;
int main()
{
    pid_t id = fork();
    if (id < 0)
    {
        perror("fork");
        exit(1); 
    }
    else if (id == 0)
    {
        //子进程
        int cnt = 5;
        while (cnt)
        {
            printf("cnt: %d, 我是子进程, pid: %d, ppid : %d\n", cnt, getpid(), getppid());
            sleep(1);
            cnt--;
        }
        code = 15;
        exit(15);
    }
    else
    {
        //父进程
        printf("我是父进程, pid: %d, ppid: %d\n", getpid(), getppid());
        int status = 0;
        pid_t ret = waitpid(id, &status, 0); //阻塞式的等待
        if (ret > 0)
        {   
            int signal_number = status & 0x7F;
            int exit_code = (status >> 8) & 0xFF;

            printf("等待子进程改变状态, ret: %d, 子进程收到的信号编号: %d, 子进程退出码: %d\n", ret, signal_number, exit_code);
            printf("code: %d\n", code);
        }
    }
}
  • 答案是不行. 已知父进程通过wait/waitpid可以拿到子进程的退出结果(退出码+收到的信号)
  • 1. 父进程 子进程均有全局变量code 初始值均为0 当子进程对code修改发生写时拷贝 父进程去访问code时是他自己的code仍为0
  • 2. 即便你显示传一个值把他作为退出码 你怎么直到他的退出码是几? 就是说 退出码是显示子进程的运行结果的 你不知道它结果正确或是发生错误 进而也就不知道他的退出码是几
  • 3. 即便你可以拿到退出码 也拿不到子进程收到的信号

2.进程具有独立性 退出码是子进程的数据 父进程是如何拿到退出码的

僵尸进程: 子进程已死亡等待父进程读取状态
在这里插入图片描述

  1. 僵尸进程的代码和数据已经释放但是PCB还存在 且 task_struct里的int exit_code, exit_signal:字段保留了进程退出时的退出码和退出信号(不仅是僵尸进程的PCB会保留 任何进程退出时都会把退出码和退出信号保留在)int exit_code, exit_signal:
  2. 那么系统调用接口wait/waitpid可以读取子进程的PCB里的int exit_code, exit_signal:把退出码和退出信号以位图形式存在status中 然后父进程就可以再反向获取
  3. 父进程没办法直接获取子进程的信息 但是可以通过调用系统接口来获取

3.对内存泄露的认识

  1. 用户写的C/C++程序中malloc/new的空间当进程终止 即便这些空间有泄露 OS也已经回收
  2. 子进程死亡父进程如果一直不读取子进程的退出状态 那么子进程将一直处于Z状态 子进程的PCB属于内核数据结构 需要OS来释放 如果父进程不管 将影响其他进程

4.对于上文中提到的系统设置的宏

  1. linux是用C语言写的 linux将自己一些系统调用接口二次封装成函数提供使用 除了一些系统调用接口/函数外 还有一些宏定义 比如WNOHANG
  2. 一些运维的程序员会说如这个进程hang住了之类的话 其实就是我们通常说的卡了 可能是在等待某种资源如网络/磁盘等 可能是进程太多了 CPU忙不过来了 这个进程hang住了 说这个进程hang住 就是说它要么在阻塞队列中 要么等待CPU调度

grep -ER 'WNOHANG' /usr/include/

grep -ER是一个Linux命令,用于在文件中递归搜索指定的字符串模式。其中,E表示使用扩展正则表达式,R表示递归搜索子目录。下面是一个例子:

假设我们有一个名为test.txt的文件,内容如下:

hello world
hello grep

我们可以使用grep -ER命令来搜索包含“hello”的行:

grep -ER "hello" test.txt

输出结果为:

test.txt:hello world
test.txt:hello grep

如果我们只想搜索以“h”开头的行,可以使用正则表达式“^h”:

grep -ER "^h" test.txt

输出结果为:

test.txt:hello world
test.txt:hello grep

如果我们想要搜索以“h”开头并且包含“o”的行,可以使用正则表达式“^h.*o”:

grep -ER "^h.*o" test.txt

输出结果为:

test.txt:hello world

5.阻塞等待和非阻塞等待

1先来回顾一下阻塞状态和挂起状态的知识

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. 阻塞等待一般是在内核中阻塞 等待资源就绪去完成自己的工作 而一个进程一直在阻塞即资源一直无法就绪 这个进程的代码和数据就被移到磁盘SWAP分区了 即此进程被切换到挂起状态了
  2. 非阻塞等待: 父进程调用waitpid来等待子进程 如果子进程没有退出 他不像阻塞等待那样一直等着直至父进程被挂起还在等 他会直接返回 这样父进程在子进程运行的这一段时间还可以做其他事情 只不过中途需要再去看看子进程运行状况(下面讲怎么中途查看)
  3. 关于网络部分的代码 大部分时IO类别 阻塞和非阻塞接口非常多
    在这里插入图片描述

对进程等待的认识

  1. 在阻塞式等待下 只有子进程退出的时候,waitpid函数才会进行返回父进程只是挂起 仍然存活
  2. waitpid/wait 让进程退出具有一定的顺序性 将来可以让父进程进行更多的收尾工作.

6.status相关宏加入后的简便写法

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

        int main() 
        {
            pid_t id = fork();
            if (id < 0) 
            {
                printf("创建子进程失败!\n");
                exit(1);
            }
            else if (id == 0)
            {
                printf("我是子进程: pid: %d,我将异常退出!\n", getpid());
                int* p = NULL;
                *p = 1; // 引发异常
            }
            else 
            {
                printf("我是父进程: pid: %d,我将耐心地等待子进程!\n", getpid());
                int status = 0;
                pid_t ret = waitpid(id, &status, 0);
                if (ret > 0)
                {
                    // 等待成功
                    
                    //printf("父进程等待成功, 退出码: %d, 退出信号: %d\n", (status>>8)&0xFF, status & 0x7F);
                    if (WIFEXITED(status))
                    {
                        printf("子进程正常退出,退出码: %d\n", WEXITSTATUS(status));
                    }
                    else if (WIFSIGNALED(status)) 
                    {
                        printf("子进程异常退出,信号编号: %d\n", WTERMSIG(status));
                    }
                }
            }
            return 0;
        }

7.非阻塞式等待的实现

当子进程未退出 父进程可以处理其他事务 父进程要处理的事务不是一次就确定好的 可能在日后会有新的事务添加进来 为了封装/降低耦合 可以设置指针数组/回调函数 只需要编写Load函数 就可以增加父进程的任务单

typedef void (*pfunc)(); //函数指针类型

std::vector<pfunc> vfunc; //函数指针数组

void fun_one()
{
    printf("临时任务1\n");
}
void fun_two()
{
    printf("临时任务2\n");
}


void Load()
{
    vfunc.push_back(fun_one);
    vfunc.push_back(fun_two);
}

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        // 子进程
        int cnt =  5;
        while(cnt)
        {
            printf("我是子进程: %d\n", cnt--);
            sleep(1);
        }

        exit(123); // 123: 测试 无意义
    }
    else
    {
        int quit = 0;

        while(!quit)
        {
            int status = 0;
            pid_t res = waitpid(-1, &status, WNOHANG); //非阻塞方式等待
            if(res > 0)
            {
                //函数调用成功 && 子进程已退出
                printf("等待子进程退出成功, 退出码: %d\n", WEXITSTATUS(status));
                quit = 1;
            }
            else if( res == 0 )
            {
                //函数调用成功 && 子进程未退出
                printf("子进程未退出,父进程可以处理其他事务\n");
                if(vfunc.empty()) 
                    Load();
                for(auto func : vfunc)
                {
                    func();
                }
            }
            else
            {
                //等待失败
                printf("函数调用失败!\n");
                quit = 1;
            }
            sleep(1);
        }
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【brpc学习实践十】streaming log实战

实战实例 通常我们在服务还没正式起来时&#xff0c;会用brpc流式log打印&#xff0c;支持对日志输出到ostream对象中&#xff08;默认std)。同时会在服务初始化时配置LogSink&#xff0c;实现自己的log&#xff0c;这样后续都可以将输出重定向至自己的log. int init(int arg…

【C++】类型转换 ③ ( 重新解释类型转换 reinterpret_cast | 指针类型数据转换 )

文章目录 一、重新解释类型转换 reinterpret_cast1、指针数据类型转换 - C 语言隐式类型转换报错 ( 转换失败 )2、指针数据类型转换 - C 语言显示类型强制转换 ( 转换成功 )3、指针数据类型转换 - C 静态类型转换 static_cast ( 转换失败 )4、指针数据类型转换 - C 重新解释类型…

有关HarmonyOS-ArkTS的Http通信请求

一、Http简介 HTTP&#xff08;Hypertext Transfer Protocol&#xff09;是一种用于在Web应用程序之间进行通信的协议&#xff0c;通过运输层的TCP协议建立连接、传输数据。Http通信数据以报文的形式进行传输。Http的一次事务包括一个请求和一个响应。 Http通信是基于客户端-服…

一盏茶的时间,入门 Node.js

一、.什么是 Node.js&#xff1f; Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时&#xff0c;用于构建高性能、可伸缩的网络应用。 它采用事件驱动、非阻塞 I/O 模型&#xff0c;使其在处理并发请求时表现出色。 二、安装 Node.js 首先&#xff0c;让我们从 Node.…

为社会做贡献的EasyDarwin 4.0.1发布了,支持视频点播、文件直播、摄像机直播、直播录像、直播回放、录像MP4合成下载

经过几个月的不懈努力和测试&#xff0c;最新的EasyDarwin 4.0版本总算是发布出来了&#xff0c;功能还是老几样&#xff1a;文件点播、视频直播&#xff08;支持各种视频源&#xff09;、直播录像与回放、录像合成MP4下载&#xff0c;稍稍看一下细节&#xff1a; 文件上传与点…

【代码随想录刷题】Day18 二叉树05------延伸题目练习

文章目录 1.【113】路径总和II1.1 题目描述1.2 解题思路1.3 java代码实现 2.【105】从前序与中序遍历序列构造二叉树2.1 题目描述2.2 java代码实现 【113】路径总和II 【105】从前序与中序遍历序列构造二叉树 1.【113】路径总和II 1.1 题目描述 给你二叉树的根节点 root 和一…

win11任务栏居中/靠左设置路径

win11任务栏居中/靠左设置路径 设置-个性化-任务栏-任务栏对齐方式

Java | The last packet sent successfully to the server was xxx milliseconds ago

最近在部署代码后&#xff0c;后端总是会遇到这个问题&#xff0c;设备通道在访问数据库时经常会报错&#xff0c;在搜集大量资料后我以为是配置问题&#xff0c;首先要保证&#xff1a; &#xff08;1&#xff09;首先确定jdbc.url地址是正确的 &#xff08;2&#xf…

林曦的小世界:不在担心与顾虑中蹉跎时间,Just Do It

内容来自林曦的小世界      先提问&#xff1a;你有没有过这样的经历&#xff0c;一项很想学的技艺&#xff0c;一件想做许久的事情&#xff0c;却始终下不了决心&#xff0c;担心左&#xff0c;担心右&#xff0c;彷徨犹豫间&#xff0c;时间过去了&#xff0c;这件事仍未…

JsonRPC协议详解(协议介绍、请求示例、响应示例)

JsonRPC协议详解 什么是RPC&#xff1f; RPC&#xff08;远程过程调用&#xff09;是一种用于实现分布式系统中不同进程或不同计算机之间通信的技术。它允许我们像调用本地函数一样调用远程计算机上的函数&#xff0c;使得分布式系统的开发变得更加简单和高效。 什么是JsonRP…

【Docker】从零开始:11.Harbor搭建企业镜像仓库

【Docker】从零开始&#xff1a;11.Harbor搭建企业镜像仓库 1. Harbor介绍2. 软硬件要求(1). 硬件要求(2). 软件要求 3.Harbor优势4.Harbor的误区5.Harbor的几种安装方式6.在线安装(1).安装composer(2).配置内核参数,开启路由转发(3).下载安装包并解压(4).创建并修改配置文件(5…

WebUI自动化学习(Selenium+Python+Pytest框架)001

开启另一篇学习之路_WebUI自动化 先来一波基础概念 1.自动化适合什么类型的项目: 重复性高,迭代频率高的回归测试。数据量大、手工难以实现的压力测试&#xff0c;手工执行效率低的兼容测试 2.自动化的优点: 高效率、可重复、减少人为错误、克服手工测试的局限性 3.自动化…

Java基于SpringBoot+vue的租房网站设计与实现(V2.0)

文章目录 一、前言介绍二、主要技术三、系统设计&#xff08;部分&#xff09;3.1、主要功能模块设计3.2、系统登录设计 四、数据库设计&#xff08;部分&#xff09;五、运行截图5.1、 **管理员** **登录****5.2、管理员功能模块**5.2.1、用户管理5.2.2、房屋类型管理5.2.3、房…

CountDownLatch实战应用——批量数据多线程协调异步处理(子线程执行事务回滚)

&#x1f60a; 作者&#xff1a; 一恍过去 &#x1f496; 主页&#xff1a; https://blog.csdn.net/zhuocailing3390 &#x1f38a; 社区&#xff1a; Java技术栈交流 &#x1f389; 主题&#xff1a; CountDownLatch实战应用——批量数据多线程协调异步处理(子线程执行事务…

Docker 部署 Nacos(单机),利用 MySQL 数据库存储配置信息

前面的话 默认你已经懂 Docker、docker-compose Nacos版本&#xff1a;v2.2.3 MySQL 版本&#xff1a;8.2.0 一、下载 打开 Nacos 官网 官网地址&#xff1a;官网 点击手册 左侧 Nacos Docker 克隆项目到本地 # 克隆项目&#xff0c;如果提示连接不到 github 请自行解决 …

【数据结构】树的概念以及二叉树

目录 1 树概念及结构 1.1 树的概念 1.3 树的存储 2 二叉树的概念及结构 2.1 概念 2.2 特殊的二叉树 2.3 二叉树的性质 2.4 二叉树的存储结构 1 树概念及结构 1.1 树的概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组…

「Verilog学习笔记」非整数倍数据位宽转换24to128

专栏前言 本专栏的内容主要是记录本人学习Verilog过程中的一些知识点&#xff0c;刷题网站用的是牛客网 要实现24bit数据至128bit数据的位宽转换&#xff0c;必须要用寄存器将先到达的数据进行缓存。24bit数据至128bit数据&#xff0c;相当于5个输入数据第6个输入数据的拼接成一…

AR眼镜双目光波导/主板硬件方案

AR(增强现实)技术的发展离不开光学元件&#xff0c;而在其中&#xff0c;光波导和Micro OLED被视为AR眼镜光学方案的黄金搭档。光学元件在AR行业中扮演着核心角色&#xff0c;其成本高昂且直接影响用户体验的亮度、清晰度和大小等因素。AR眼镜的硬件成本中&#xff0c;光机部分…

测试工程师必学看系列之Jmeter_性能测试:性能测试的流程和术语

性能测试的流程 一、准备工作 1、系统基础功能验证 一般情况下&#xff0c;只有在系统基础功能测试验证完成、系统趋于稳定的情况下&#xff0c;才会进行性能测试&#xff0c;否则性能测试是无意义的。2、测试团队组建 根据该项目的具体情况&#xff0c;组建一个几人的性能测试…

【刷题宝典NO.5】

有效的括号 https://leetcode.cn/problems/valid-parentheses/ 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 左括号必须用相同类型的右括号闭合。左括号必…