面试中遇到的一些有关进程的问题(有争议版)

news2025/1/16 4:49:09

一个进程最多可以创建多少个线程?

这个面经很有问题,没有说明是什么操作系统,以及是多少位操作系统。

因为不同的操作系统和不同位数的操作系统,虚拟内存可能是不一样多。

Windows 系统我不了解,我就说说 Linux 系统。

在 Linux 操作系统中,虚拟地址空间的内部又被分为内核空间和用户空间两部分,不同位数的系 统,地址 空间的范围也不同。比如最常⻅的 32 位和 64 位系统,如下所示:

  • 通过这里可以看出: 32 位系统的内核空间占用 1G ,位于最高处,剩下的 3G 是用户空间;
  • 64 位系统的内核空间和用户空间都是 128T ,分别占据整个内存空间的最高和最低处,剩下的 中 间部分是未定义的。

接着,来看看读者那个面经题目:一个进程最多可以创建多少个线程? 这个问题跟两个东西有关系:

  • 进程的虚拟内存空间上限,因为创建一个线程,操作系统需要为其分配一个栈空间,如果线程数量越多,所需的栈空间就要越大,那么虚拟内存就会占用的越多。
  • 系统参数限制,虽然 Linux 并没有内核参数来控制单个进程创建的最大线程个数,但是有系统级别的参数来控制整个系统的最大线程个数。

我们先看看,在进程里创建一个线程需要消耗多少虚拟内存大小?

我们可以执行 ulimit -a 这条命令,查看进程创建线程时默认分配的栈空间大小,比如我这台服务 器默认分配给线程的栈空间大小为 8M。

在前面我们知道,在 32 位 Linux 系统里,一个进程的虚拟空间是 4G,内核分走了1G,留给用户 用的只有 3G

那么假设创建一个线程需要占用 10M 虚拟内存,总共有 3G 虚拟内存可以使用。于是我们可以算 出,最多可以创建差不多 300 个(3G/10M)左右的线程。

如果你想自己做个实验,你可以找台 32 位的 Linux 系统运行下面这个代码

由于我手上没有 32 位的系统,我这里贴一个网上别人做的测试结果:

如果想使得进程创建上千个线程,那么我们可以调整创建线程时分配的栈空间大小,比如调整为 512k:

$ ulimit -s 512

说完 32 位系统的情况,我们来看看 64 位系统里,一个进程能创建多少线程呢?

我的测试服务器的配置:

  • 64 位系统;
  • 2G 物理内存;
  • 单核 CPU。

64 位系统意味着用户空间的虚拟内存最大值是 128T,这个数值是很大的,如果按创建一个线程需 占用 10M 栈空间的情况来算,那么理论上可以创建 128T/10M 个线程,也就是 1000多万个线 程,有点魔幻!

所以按 64 位系统的虚拟内存大小,理论上可以创建无数个线程。

事实上,肯定创建不了那么多线程,除了虚拟内存的限制,还有系统的限制。

比如下面这三个内核参数的大小,都会影响创建线程的上限:

  • /proc/sys/kernel/threads-max,表示系统支持的最大线程数,默认值是 14553 ;
  • /proc/sys/kernel/pid_max,表示系统全局的 PID 号数值的限制,每一个进程或线程都有 ID, ID 的值超过这个数,进程或线程就会创建失败,默认值是 32768 ;
  • /proc/sys/vm/max_map_count,表示限制一个进程可以拥有的VMA(虚拟内存区域)的数量,具 体什么意思我也没搞清楚,反正如果它的值很小,也会导致创建线程失败,默认值是 65530 

那接下针对我的测试服务器的配置,看下一个进程最多能创建多少个线程呢? 我在这台服务器跑了前面的程序,其结果如下:

$ ulimit -s 512 可以看到,创建了 14374 个线程后,就无法再创建了,而且报错是因为资源的限制。

前面我提到的 threads-max 内核参数,它是限制系统里最大线程数,默认值是 14553。

我们可以运行那个测试线程数的程序后,看下当前系统的线程数是多少,可以通过 top -H查看。

左上角的 Threads 的数量显示是 14553,与 threads-max 内核参数的值相同,所以我们可以认为 是因为这个参数导致无法继续创建线程。

那么,我们可以把 threads-max 参数设置成 99999 :

echo 99999 > /proc/sys/kernel/threads-max

设置完 threads-max 参数后,我们重新跑测试线程数的程序,运行后结果如下图: echo 99999 > /proc/sys/kernel/threads-max 可以看到,当进程创建了 32326 个线程后,就无法继续创建里,且报错是无法继续申请内存。

此时的上限个数很接近 pid_max 内核参数的默认值(32768),那么我们可以尝试将这个参数设 置为 99999:

echo 99999 > /proc/sys/kernel/pid_max

设置完 pid_max 参数后,继续跑测试线程数的程序,运行后结果创建线程的个数还是一样卡在了 32768 了。

当时我也挺疑惑的,明明 pid_max 已经调整大后,为什么线程个数还是上不去呢?

后面经过查阅资料发现, max_map_count 这个内核参数也是需要调大的,但是它的数值与最大线 程数之间有什么关系,我也不太明白,只是知道它的值是会限制创建线程个数的上限。

然后,我把 max_map_count 内核参数也设置成后 99999:

echo 99999 > /proc/sys/kernel/max_map_count 

继续跑测试线程数的程序,结果如下图:

当创建差不多 5 万个线程后,我的服务器就卡住不动了,CPU 都已经被占满了,毕竟这个是单核 CPU,所以现在是 CPU 的瓶颈了。

我只有这台服务器,如果你们有性能更强的服务器来测试的话,有兴趣的小伙伴可以去测试下。

接下来,我们换个思路测试下,把创建线程时分配的栈空间调大,比如调大为 100M,在大就会创 建线程失败。

ulimit -s 1024000

设置完后,跑测试线程的程序,其结果如下:

总共创建了 26390 个线程,然后就无法继续创建了,而且该进程的虚拟内存空间已经高达 25T, 要知道这台服务器的物理内存才 2G。

为什么物理内存只有 2G,进程的虚拟内存却可以使用 25T 呢?

因为虚拟内存并不是全部都映射到物理内存的,程序是有局部性的特性,也就是某一个时间只会执行部分代码,所以只需要映射这部分程序就好。

你可以从上面那个 top 的截图看到,虽然进程虚拟空间很大,但是物理内存(RES)只有使用了 400 多M。

总结

好了,简单总结下:

  • 32 位系统,用户态的虚拟空间只有 3G,如果创建线程时分配的栈空间是 10M,那么一个进程 最多只能创建 300 个左右的线程。
  • 64 位系统,用户态的虚拟空间大到有 128T,理论上不会受虚拟内存大小的限制,而会受系统 的参数或性能限制。

线程崩溃了,进程也会崩溃吗?

很多同学就好奇,为什么 C/C++ 语言里,线程崩溃后,进程也会崩溃,而 Java 语言里却不会 呢?

本文分以下几节来探讨:

  • 1. 线程崩溃,进程一定会崩溃吗
  • 2. 进程是如何崩溃的-信号机制简介
  • 3. 为什么在 JVM 中线程崩溃不会导致 JVM 进程崩溃
  • 4. openJDK 源码解析

线程崩溃,进程一定会崩溃吗?

一般来说如果线程是因为非法访问内存引起的崩溃,那么进程肯定会崩溃,为什么系统要让进程 崩溃呢,这主要是因为在进程中,各个线程的地址空间是共享的,既然是共享,那么某个线程对 地址的非法访问就会导致内存的不确定性,进而可能会影响到其他线程,这种操作是危险的,操 作系统会认为这很可能导致一系列严重的后果,于是干脆让整个进程崩溃

线程共享代码段,数据段,地址空间,文件非法访问内存有以下几种情况,我们以 C 语言举例来 看看。

1.、针对只读内存写入数据

2、访问了进程没有权限访问的地址空间(比如内核空间)

在 32 位虚拟地址空间中,p 指向的是内核空间,显然不具有写入权限,所以上述赋值操作会导致 崩溃

3、访问了不存在的内存,比如:

以上错误都是访问内存时的错误,所以统一会报 Segment Fault 错误(即段错误),这些都会导致 进程崩溃

进程是如何崩溃的-信号机制简介

那么线程崩溃后,进程是如何崩溃的呢,这背后的机制到底是怎样的,答案是信号

大家想想要干掉一个正在运行的进程是不是经常用 kill -9 pid 这样的命令,这里的 kill 其实就是给 指定 pid 发送终止信号的意思,其中的 9 就是信号。

其实信号有很多类型的,在 Linux 中可以通过 kill -l 查看所有可用的信号:

当然了发 kill 信号必须具有一定的权限,否则任意进程都可以通过发信号来终止其他进程,那显然 是不合理的,实际上 kill 执行的是系统调用,将控制权转移给了内核(操作系统),由内核来给指 定的进程发送信号

那么发个信号进程怎么就崩溃了呢,这背后的原理到底是怎样的?

其背后的机制如下

1. CPU 执行正常的进程指令

2. 调用 kill 系统调用向进程发送信号

3. 进程收到操作系统发的信号,CPU 暂停当前程序运行,并将控制权转交给操作系统

4. 调用 kill 系统调用向进程发送信号(假设为 11,即 SIGSEGV,一般非法访问内存报的都是这个 错误)

5. 操作系统根据情况执行相应的信号处理程序(函数),一般执行完信号处理程序逻辑后会让进 程退出

注意上面的第五步,如果进程没有注册自己的信号处理函数,那么操作系统会执行默认的信号处 理程序(一般最后会让进程退出),但如果注册了,则会执行自己的信号处理函数,这样的话就 给了进程一个垂死挣扎的机会,它收到 kill 信号后,可以调用 exit() 来退出,但也可以使用 sigsetjmp,siglongjmp 这两个函数来恢复进程的执行

如代码所示:注册信号处理函数后,当收到 SIGSEGV 信号后,先执行相关的逻辑再退出 另外当进程接收信号之后也可以不定义自己的信号处理函数,而是选择忽略信号,如下

也就是说虽然给进程发送了 kill 信号,但如果进程自己定义了信号处理函数或者无视信号就有机会 逃出生天,当然了 kill -9 命令例外,不管进程是否定义了信号处理函数,都会马上被干掉。

说到这大家是否想起了一道经典面试题:如何让正在运行的 Java 工程的优雅停机?

通过上面的介绍大家不难发现,其实是 JVM 自己定义了信号处理函数,这样当发送 kill pid 命令 (默认会传 15 也就是 SIGTERM)后,JVM 就可以在信号处理函数中执行一些资源清理之后再调 用 exit 退出。

这种场景显然不能用 kill -9,不然一下把进程干掉了资源就来不及清除了。

为什么线程崩溃不会导致JVM进程崩溃

现在我们再来看看开头这个问题,相信你多少会心中有数,想想看在 Java 中有哪些是常见的由于 非法访问内存而产生的 Exception 或 error 呢,常见的是大家熟悉的 StackoverflowError 或者 NPE (NullPointerException),NPE 我们都了解,属于是访问了不存在的内存。

但为什么栈溢出(Stackoverflow)也属于非法访问内存呢,这得简单聊一下进程的虚拟空间,也 就是前面提到的共享地址空间。

现代操作系统为了保护进程之间不受影响,所以使用了虚拟地址空间来隔离进程,进程的寻址都 是针对虚拟地址,每个进程的虚拟空间都是一样的,而线程会共用进程的地址空间。

以 32 位虚拟空间,进程的虚拟空间分布如下:

那么 stackoverflow 是怎么发生的呢?

进程每调用一个函数,都会分配一个栈桢,然后在栈桢里会分配函数里定义的各种局部变量。

假设现在调用了一个无限递归的函数,那就会持续分配栈帧,但 stack 的大小是有限的(Linux 中 默认为 8 M,可以通过 ulimit -a 查看),如果无限递归很快栈就会分配完了,此时再调用函数试 图分配超出栈的大小内存,就会发生段错误,也就是 stackoverflowError。

好了,现在我们知道了 StackoverflowError 怎么产生的。

那问题来了,既然 StackoverflowError 或者 NPE 都属于非法访问内存, JVM 为什么不会崩溃呢?

有了上一节的铺垫,相信你不难回答,其实就是因为 JVM 自定义了自己的信号处理函数,拦截了 SIGSEGV 信号,针对这两者不让它们崩溃。

怎么证明这个推测呢,我们来看下 JVM 的源码来一探究竟

openJDK源码解析

HotSpot 虚拟机目前使用范围最广的 Java 虚拟机,据 R 大所述, Oracle JDK 与 OpenJDK 里的 JVM 都是 HotSpot VM,从源码层面说,两者基本上是同一个东西。

OpenJDK 是开源的,所以我们主要研究下 Java 8 的 OpenJDK 即可,地址如下: https://github.com/AdoptOpenJDK/openjdk-jdk8u ,有兴趣的可以下载来看看。

我们只要研究 Linux 下的 JVM,为了便于说明,也方便大家查阅,我把其中关于信号处理的关键 流程整理了下(忽略其中的次要代码)。

可以看到,在启动 JVM 的时候,也设置了信号处理函数,收到 SIGSEGV,SIGPIPE 等信号后最终 会调用 JVM_handle_linux_signal 这个自定义信号处理函数,再来看下这个函数的主要逻辑。

JVM_handle_linux_signal(int sig, siginfo_t* info, void* ucVoid, int abort_if_unrecognized) {
    // Must do this before SignalHandlerMark, if crash protection installed we will longjmp
    os::ThreadCrashProtection::check_crash_protection(sig, t);
    if (info != NULL && uc != NULL && thread != NULL) {
        pc = (address) os::Linux::ucontext_get_pc(uc);
        // Handle ALL stack overflow variations here
        if (sig == SIGSEGV) {
            // Si_addr may not be valid due to a bug in the linux-ppc64 kernel (see comment below).
            // Use get_stack_bang_address instead of si_addr.
            address addr = ((NativeInstruction*)pc)->get_stack_bang_address(uc);
            // 判断是否栈溢出了
            if (addr < thread->stack_base() &&
                addr >= thread->stack_base() - thread->stack_size()) {
                if (thread->thread_state() == _thread_in_Java) { // 针对栈溢出 JVM 的内部处理
                    stub = SharedRuntime::continuation_for_implicit_exception(thread, pc, SharedRuntime::null_check);
                }
            }
        }
        if (sig == SIGSEGV &&
            !MacroAssembler::needs_explicit_null_check((intptr_t)info->si_addr)) {
            // 此处会做空指针检查
            stub = SharedRuntime::continuation_for_implicit_exception(thread, pc, SharedRuntime::null_check);
        }
        // 如果是栈溢出或者空指针最终会返回 true,不会走最后的 report_and_die,所以 JVM 不会退出
        if (stub != NULL) {
            // save all thread context in case we need to restore it
            if (thread != NULL) thread->set_saved_exception_pc(pc);
            uc->uc_mcontext.gregs[REG_PC] = (greg_t)stub;
            // 返回 true 代表 JVM 进程不会退出
            return true;
        }
    }
    VMError err(t, sig, pc, info, ucVoid);
    // 生成 hs_err_pid_xxx.log 文件并退出
    err.report_and_die();
    ShouldNotReachHere();
    return true; // Mute compiler
}

从以上代码我们可以知道以下信息:

1. 发生 stackoverflow 还有空指针错误,确实都发送了 SIGSEGV,只是虚拟机不选择退出,而是自 己内部作了额外的处理,其实是恢复了线程的执行,并抛出 StackoverflowError 和 NPE,这就 是为什么 JVM 不会崩溃且我们能捕获这两个错误/异常的原因

2. 如果针对 SIGSEGV 等信号,在以上的函数中 JVM 没有做额外的处理,那么最终会走到 report_and_die 这个方法,这个方法主要做的事情是生成 hs_err_pid_xxx.log crash 文件(记录 了一些堆栈信息或错误),然后退出

至此我相信大家明白了为什么发生了 StackoverflowError 和 NPE 这两个非法访问内存的错误, JVM 却没有崩溃。

原因其实就是虚拟机内部定义了信号处理函数,而在信号处理函数中对这两者做了额外的处理以 让 JVM 不崩溃,另一方面也可以看出如果 JVM 不对信号做额外的处理,最后会自己退出并产生crash 文件hs_err_pid_xxx.log(可以通过 -XX:ErrorFile=/var/log/hs_err.log 这样的方式指定) 这个文件记录了虚拟机崩溃的重要原因。

所以也可以说,虚拟机是否崩溃只要看它是否会产生此崩溃日志文件

总结

正常情况下,操作系统为了保证系统安全,所以针对非法内存访问会发送一个 SIGSEGV 信号,而 操作系统一般会调用默认的信号处理函数(一般会让相关的进程崩溃)。

但如果进程觉得"罪不致死",那么它也可以选择自定义一个信号处理函数,这样的话它就可以做一 些自定义的逻辑,比如记录 crash 信息等有意义的事。

回过头来看为什么虚拟机会针对 StackoverflowError 和 NullPointerException 做额外处理让线程恢 复呢,针对 stackoverflow 其实它采用了一种栈回溯的方法保证线程可以一直执行下去,而捕获空 指针错误主要是这个错误实在太普遍了。

为了这一个很常见的错误而让 JVM 崩溃那线上的 JVM 要宕机多少次,所以出于工程健壮性的考 虑,与其直接让 JVM 崩溃倒不如让线程起死回生,并且将这两个错误/异常抛给用户来处理。

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

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

相关文章

Excel技巧:如何批量调整excel表格中的图片?

插入到excel表格中的图片大小不一&#xff0c;如何做到每张图片都完美的与单元格大小相同&#xff1f;并且能够根据单元格来改变大小&#xff1f;今天分享&#xff0c;excel表格里的图片如何批量调整大小。 方法如下&#xff1a; 点击表格中的一个图片&#xff0c;然后按住Ct…

Stable Audio Open模型部署教程:用AI打造独家节拍,让声音焕发新活力!

Stable Audio Open 是一个开源的文本到音频模型&#xff0c;允许用户从简单的文本提示中生成长达 47 秒的高质量音频数据。该模型非常适合创建鼓点、乐器即兴演奏、环境声音、拟音录音和其他用于音乐制作和声音设计的音频样本。用户还可以根据他们的自定义音频数据微调模型&…

Linux上传代码的步骤与注意事项

最近因为工作需要&#xff0c;要上传代码到 DPDK 上&#xff0c;代码已经上传成功&#xff0c;记录一下过程&#xff0c;给大家提供一个参考。我这次需要上传的是pmd&#xff0c;即poll mode driver。 1 Coding Style 要上传代码&#xff0c;第一件事就是需要知道Coding Styl…

运费微服务和redis存热点数据

目录 运费模板微服务 接收前端发送的模板实体类 插入数据时使用的entity类对象 BaseEntity类 查询运费模板服务 新增和修改运费模块 整体流程 代码实现 运费计算 整体流程 总的代码 查找运费模板方法 计算重量方法 Redis存入热点数据 1.从nacos导入共享redis配置…

如何在windows10上部署WebDAV服务并通过内网穿透实现公网分享内部公共文件

WebDAV&#xff08;Web-based Distributed Authoring and Versioning&#xff09;是一种基于HTTP协议的应用层网络协议&#xff0c;它允许用户通过互联网进行文件的编辑和管理。这意味着&#xff0c;无论员工身处何地&#xff0c;只要连接到互联网&#xff0c;就能访问、编辑和…

gRPC 快速入门 — SpringBoot 实现(1)

目录 一、什么是 RPC 框架 &#xff1f; 二、什么是 gRPC 框架 &#xff1f; 三、传统 RPC 与 gRPC 对比 四、gRPC 的优势和适用场景 五、gRPC 在分布式系统中应用场景 六、什么是 Protocol Buffers&#xff08;ProtoBuf&#xff09;&#xff1f; 特点 使用场景 简单的…

深入浅出:SOME/IP-SD的工作原理与应用

目录 往期推荐 相关缩略语 SOME/IP 协议概述 协议介绍 SOME/IP TP 模块概述和 BSW 模块依赖性 原始 SOME/IP 消息的Header格式 SOME/IP-SD 模块概述 模块介绍 BSW modules依赖 客户端-服务器通信示例 Message 结构 用于SD服务的BSWM状态处理 往期推荐 ETAS工具…

字节高频算法面试题:小于 n 的最大数

问题描述&#xff08;感觉n的位数需要大于等于2&#xff0c;因为n的位数1的话会有点问题&#xff0c;“且无重复”是指nums中存在重复&#xff0c;但是最后返回的小于n最大数是可以重复使用nums中的元素的&#xff09;&#xff1a; 思路&#xff1a; 先对nums倒序排序 暴力回…

相机动态/在线标定

图1 图2 基本原理 【原理1】平行线在射影变换后会交于一点。如图所示,A为相机光心,蓝色矩形框为归一化平面,O为平面中心。地面四条黄色直线为平行且等距的车道线。HI交其中两条车道线于H、I, 过G作HI的平行线GM交车道线于M。HI、GM在归一化平面上的投影分别为JK、PN,二者会…

在 Windows 11 WSL (Ubuntu 24.04.1 LTS) | Python 3.12.x 下部署密码学库 charm

1. 在 Windows 11 上部署 Ubuntu (WSL) 由于作者没有高性能的 Ubuntu 服务器或个人电脑&#xff0c;且公司或学校提供的 Ubuntu 服务器虽然提供高性能 GPU 等硬件配置但通常不会提供 root 权限&#xff0c;因而作者通过在搭载了 Windows 11 的个人电脑上启动 Ubuntu (WSL) 来进…

【中间件开发】Redis基础命令详解及概念介绍

文章目录 前言一、Redis相关命令详解及原理1.1 string、set、zset、list、hash1.1.1 string1.1.2 list1.1.3 hash1.1.4 set1.1.5 zset 1.2 分布式锁的实现1.3 lua脚本解决ACID原子性1.4 Redis事务的ACID性质分析 二、Redis协议与异步方式2.1 Redis协议解析2.1.1 redis pipeline…

设计模式的艺术读书笔记

设计模式的艺术 面向对象设计原则概述单一职责原则开闭原则里氏代换原则依赖倒转原则接口隔离原则合成复用原则迪米特法则 创建的艺术创建型模式单例模式饿汉式单例与懒汉式单例的讨论通过静态内部类实现的更好办法 简单工厂模式工厂方法模式重载的工厂方法工厂方法的隐藏工厂方…

计算机毕设-基于springboot的甜品店管理系统的设计与实现(附源码+lw+ppt+开题报告)

博主介绍&#xff1a;✌多个项目实战经验、多个大型网购商城开发经验、在某机构指导学员上千名、专注于本行业领域✌ 技术范围&#xff1a;Java实战项目、Python实战项目、微信小程序/安卓实战项目、爬虫大数据实战项目、Nodejs实战项目、PHP实战项目、.NET实战项目、Golang实战…

Mac 录制电脑系统内的声音的具体方法?

1.第一步&#xff1a;下载BlackHole 软件 方式1&#xff1a;BlackHole官方下载地址 方式2&#xff1a; 百度云下载 提取码: n5dp 2.第二步&#xff1a;安装BlackHole 双击下载好的BlackHole安装包&#xff0c;安装默认提示安装。 3.第三步&#xff1a;在应用程序中找到音频…

【开源免费】基于Vue和SpringBoot的课程答疑系统(附论文)

博主说明&#xff1a;本文项目编号 T 070 &#xff0c;文末自助获取源码 \color{red}{T070&#xff0c;文末自助获取源码} T070&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析…

ACM latex模板中的CCSXML (即:CCS Concept)怎么填?

CCS Concept 感谢CCS Concept 怎么填 的珠玉在前. 问题描述 如下&#xff0c;ACM模板&#xff08;比如ACM computing surveys&#xff09;有一段是需要填写 ccsxml&#xff1a; %% %% The code below is generated by the tool at [http://dl.acm.org/ccs.cfm.](http://dl.…

【Transformer序列预测】Pytorch中构建Transformer对序列进行预测源代码

Python&#xff0c;Pytorch中构建Transformer进行序列预测源程序。包含所有的源代码和数据&#xff0c;程序能够一键运行。此程序是完整的Transformer&#xff0c;即使用了Encoder、Decoder和Embedding所有模块。源程序是用jupyterLab所写&#xff0c;建议分块运行。也整理了.p…

Mybatis-plus 简单使用,mybatis-plus 分页模糊查询报500 的错

一、mybtis-plus配置下载 MyBatis-Plus 是一个 Mybatis 增强版工具&#xff0c;在 MyBatis 上扩充了其他功能没有改变其基本功能&#xff0c;为了简化开发提交效率而存在。 具体的介绍请参见官方文档。 官网文档地址&#xff1a;mybatis-plus 添加mybatis-plus依赖 <depe…

前端项目使用gitlab-cicd+docker实现自动化部署

GitLab CI/CD 是一个强大的工具&#xff0c;可以实现项目的自动化部署流程&#xff0c;从代码提交到部署只需几个步骤。本文将带你配置 GitLab CI/CD 完成一个前端项目的自动化部署。 前言 为什么使用cicddocker&#xff1f; 目前我们公司开发环境使用的shell脚本部署&#…

设计模式:20、状态模式(状态对象)

目录 0、定义 1、状态模式的三种角色 2、状态模式的UML类图 3、示例代码 0、定义 允许一个对象在其内部状态改变时改变它的行为&#xff0c;对象看起来似乎修改了它的类。 1、状态模式的三种角色 环境&#xff08;Context&#xff09;&#xff1a;环境是一个类&#xff0…