JDK19 - synchronized关键字导致的虚拟线程PINNED

news2024/10/1 15:18:07

JDK19 - synchronized关键字导致的虚拟线程PINNED

  • 前言
  • 一. PINNED是什么意思
    • 1.1 synchronized 绑定测试
    • 1.2 synchronized 关键字的替代
  • 二. -Djdk.tracePinnedThreads的作用和坑
    • 2.1 死锁案例测试
    • 2.2 发生原因的推测
    • 2.3 总结

前言

在 虚拟线程详解 这篇文章里面,我们详解了虚拟线程的一个执行原理和底层执行顺序。那么这里我们分享一下一个使用虚拟线程的坑点。

一. PINNED是什么意思

PINNED指的是绑定,意思是虚拟线程无法在阻塞操作期间卸载,而被固定到其运载线程。 JEP425给出的说明中,提到了两种发生pinned的情况:

在这里插入图片描述

  1. 当调用的代码中被synchronized关键字修饰。
  2. 执行native methodforeign function

1.1 synchronized 绑定测试

案例代码:

public class Main {
    /**
     * 用于测试同步锁的对象
     */
    private static volatile Object instance = new Object();
    /**
     * 用于格式化时间
     */
    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    /**
     * 执行任务
     */
    private static void runTask(int threadNum) {
        realRunTask(threadNum);
    }

    /**
     * 执行任务(加锁)
     * @param threadNum
     */
    private static void runTaskWithSynchronized(int threadNum) {
        synchronized (instance) {
            realRunTask(threadNum);
        }
    }

    // Calendar 转 yyyy-MM-dd HH:mm:ss
    public static String format(Calendar calendar) {
        return sdf.format(calendar.getTime());
    }

    private static void realRunTask(int threadNum) {
        System.out.printf("%s|Test is start ThreadNum is %s %s%n", Thread.currentThread(), threadNum, format(Calendar.getInstance()));
        try {
            Thread.sleep(1000);
        } catch (Exception e) {

        }
        System.out.printf("%s|Test is Over ThreadNum is  %s %s%n", Thread.currentThread(), threadNum, format(Calendar.getInstance()));
    }

    private static ExecutorService getExecutorService(boolean isVirtualThread, boolean useThreadPool) {
        if (useThreadPool) {
            return new ThreadPoolExecutor(50, 50, 1, TimeUnit.MINUTES,
                    new ArrayBlockingQueue<>(100000),
                    isVirtualThread ? Thread.ofVirtual().factory() : Thread.ofPlatform().factory());
        } else {
            ThreadFactory factory = isVirtualThread ?
                    Thread.ofVirtual().name("This-Test-Virtual-Thread-", 0).factory() : Thread.ofPlatform().name(
                    "This-Test-Platform-Thread-", 0).factory();
            return Executors.newThreadPerTaskExecutor(factory);
        }
    }

    /**
     * -Djdk.tracePinnedThreads=full
     * -Djdk.virtualThreadScheduler.parallelism=1
     * -Djdk.virtualThreadScheduler.maxPoolSize=1
     * -Djdk.virtualThreadScheduler.minRunnable=1
     *
     * -Djdk.tracePinnedThreads=full -Djdk.virtualThreadScheduler.parallelism=1 -Djdk.virtualThreadScheduler.maxPoolSize=2 -Djdk.virtualThreadScheduler.minRunnable=1
     */
    public static void main(String[] args) throws Exception{
        ExecutorService executorService = getExecutorService(true, false);
        Future task1 = executorService.submit(() -> runTaskWithSynchronized(1));
        Future task2 = executorService.submit(() -> runTask(2));
        executorService.close();
        task1.get();
        task2.get();
    }
}

分析:

  1. 我们有用于测试同步锁的对象instance,专门拿来给synchronized关键字用的。
  2. 两种执行任务方式:一种普通的,一种加锁的。
  3. 执行的任务做了什么:睡眠了一秒钟,并且打印相关数据。
  4. 我们同时启动两个task,看看最终的结果是什么。

我们在启动之前,给Main函数添加一些参数:

  1. -Djdk.tracePinnedThreads=full:开启对虚拟线程的跟踪。设置为"full"表示输出详细的虚拟线程信息,包括线程ID、状态和执行时间等。这样被pinned的时候,我们就可以通过打印的信息观察到了 (后面有惊喜)

  2. -Djdk.virtualThreadScheduler.parallelism=1:这个参数指定了虚拟线程调度器的并行度。并行度表示同时执行虚拟线程的最大数量。在这里,设置为1表示只允许一个虚拟线程同时执行。

  3. -Djdk.virtualThreadScheduler.maxPoolSize=1:这个参数指定了虚拟线程调度器的最大线程池大小。线程池是用于存放虚拟线程的容器。在这里,设置为1表示线程池的大小为1,即最多只能容纳一个虚拟线程。

  4. -Djdk.virtualThreadScheduler.minRunnable=1:这个参数指定了虚拟线程调度器的最小可运行虚拟线程数。当虚拟线程池中的可运行线程数低于这个值时,调度器会尝试创建新的虚拟线程以填充线程池。在这里,设置为1表示最小可运行线程数为1。

我们设置可执行的线程数为1:maxPoolSize=1

-Djdk.tracePinnedThreads=full -Djdk.virtualThreadScheduler.parallelism=1 -Djdk.virtualThreadScheduler.maxPoolSize=1 -Djdk.virtualThreadScheduler.minRunnable=1

那么此时由于最大只有一个可执行线程,因此按照逻辑顺序,应该是带有synchronized关键字的task1先执行,再执行task2。而因为task1synchronized关键字修饰,因此线程被pinned
在这里插入图片描述

我们设置可执行的线程数为2:maxPoolSize=2 ,那么此时两个任务可以同时提交,但是task1synchronized关键字修饰,因此线程同样被pinned

-Djdk.tracePinnedThreads=full -Djdk.virtualThreadScheduler.parallelism=1 -Djdk.virtualThreadScheduler.maxPoolSize=2 -Djdk.virtualThreadScheduler.minRunnable=1

在这里插入图片描述
注意:这两个返回结果的顺序是不一样的!

从上面的结果上来看,直观的结论就是:

  1. 如果执行代码中包含了synchronized关键字,那么这个线程将会被pinned。即任务1所在的虚拟线程无法卸载,而是被固定到了运载线程。
  2. 哪怕两个任务是“同时”提交,也会优先将任务1(被pinned的线程)执行完毕,再去启动任务2。因为任务2只能等待任务1执行完毕才能够继续执行。
  3. 那么也就失去了异步的一个概念了。

那么针对这种情况,我们如何解决?官方建议是使用Synchronized关键字的地方可以利用其他锁,比如重入锁来替代。

1.2 synchronized 关键字的替代

我们再写一个函数:

private static void runTaskWithReentrantLock(int threadNum) {
    ReentrantLock reentrantLock = new ReentrantLock();
    reentrantLock.lock();
    try {
        realRunTask(threadNum);
    } catch (Exception e) {
    } finally {
        reentrantLock.unlock();
    }
}

同时将maxPoolSize 重新设置为1,然后启动的时候变更如下:

public static void main(String[] args) throws Exception{
    ExecutorService executorService = getExecutorService(true, false);
    Future task1 = executorService.submit(() -> runTaskWithReentrantLock(1));
    Future task2 = executorService.submit(() -> runTask(2));
    executorService.close();
    task1.get();
    task2.get();
}

结果如下:可见,哪怕我们可执行的线程只有1个,但是两个任务也几乎是同时并发执行的。同时pinned的情况也不复存在。
在这里插入图片描述

二. -Djdk.tracePinnedThreads的作用和坑

我们先来说下这个参数的作用吧。在上文中,我们使用了-Djdk.tracePinnedThreads参数来打印虚拟线程pinned时相关的堆栈信息。让我们非常直观的观察到pinned的行为。

那么试想一下,我们为了去使用虚拟线程这个新特性,而进行JDK的升级。这个升级难以避免的是带来一定的风险。例如上文的synchronized关键字。它的存在可能导致你的虚拟线程无法被卸载,而进入pinned状态。那么,你的代码又有哪些隐藏的风险需要你关注呢?

  • 你的代码中是否有显式地使用synchronized关键字?
  • 你引入的外部第三方依赖中,内部操作是否同样地使用了synchronized关键字?

前者我们可以通过全局搜索,自己去在项目里面解决,但是要命的是后者,你很难做到全面排查所有的第三方依赖对synchronized关键字的使用情况。那么我们就可以增加这个参数去打印可能发生的pinned情况,一旦有,我们就可以通过堆栈信息去定位代码,然后解决。

-Djdk.tracePinnedThreads=full

但是这个情况仅仅适用于本地开发或者是测试环境的灰度阶段,并不适合发到生产。为什么呢?因为这个VM参数同样可能导致虚拟线程不可用,发生死锁。这是本文想分享的第二个重点。

2.1 死锁案例测试

添加两个依赖:

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.13</version>
</dependency>
<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.2</version>
</dependency>

贴出代码:

public class LockTest {
    /**
     * 平台线程数
     */
    static int PLATFORM_THREAD_COUNT;
    /**
     * 虚拟线程数
     */
    static int VIRTUAL_THREAD_COUNT;
    static CloseableHttpClient client;

    public static void main(String[] args) throws Exception {
        PLATFORM_THREAD_COUNT = 1;
        VIRTUAL_THREAD_COUNT = PLATFORM_THREAD_COUNT + 1;
        // 替换为这个即可解决死锁
        // VIRTUAL_THREAD_COUNT = PLATFORM_THREAD_COUNT;
        // 初始化apache http client
        client = initClient();
        // 设置虚拟线程池大小,最大线程数,最小可运行线程数 为平台线程数
        String strSize = Integer.toString(PLATFORM_THREAD_COUNT);
        System.setProperty("jdk.virtualThreadScheduler.parallelism", strSize);
        System.setProperty("jdk.virtualThreadScheduler.maxPoolSize", strSize);
        System.setProperty("jdk.virtualThreadScheduler.minRunnable", strSize);
        // 启动测试
        test();
    }

    public static void test() throws Exception {
        // 设置栅栏数为虚拟线程数
        CountDownLatch countDownLatch = new CountDownLatch(VIRTUAL_THREAD_COUNT);
        // 启动对应数量的虚拟线程任务
        for (int j = 0; j < VIRTUAL_THREAD_COUNT; j++) {
            Thread.ofVirtual().start(() -> apachePoolingHttpClient(client, countDownLatch));
        }
        // 如果任务没有执行完毕,等待,会循环打印等待信息
        while (countDownLatch.getCount() != 0) {
            System.out.println("waiting " + countDownLatch.getCount());
            Thread.sleep(2000);
        }
        // 只有虚拟线程执行完毕,才会执行下面的代码
        System.out.println("end success");
    }

    /**
     * 初始化apache http client,没什么好看的
     *
     * @return
     */
    private static CloseableHttpClient initClient() {
        PoolingHttpClientConnectionManager poolingConnManager = new PoolingHttpClientConnectionManager();
        poolingConnManager.setMaxTotal(PLATFORM_THREAD_COUNT);
        return HttpClients.custom()
                .setConnectionManager(poolingConnManager)
                .build();
    }

    /**
     * apache http client 发送请求,关注点在最后一行代码,执行IO完毕,会调用countDownLatch.countDown(),表示当前虚拟线程执行完毕
     */
    private static void apachePoolingHttpClient(CloseableHttpClient client, CountDownLatch countDownLatch) {
        HttpGet request = new HttpGet("https://www.google.com");
        try (CloseableHttpResponse execute = client.execute(request)) {
            StatusLine statusLine = execute.getStatusLine();
            System.out.println(statusLine.getStatusCode());
        } catch (Throwable e) {
            throw new RuntimeException(e);
        } finally {
            countDownLatch.countDown();
        }
    }
}

分析如下:

  1. 我们设置平台线程数为1个,虚拟线程数为2个。然后启动两个虚拟线程任务。
  2. 启动任务之前,我们初始化了一个栅栏CountDownLatch,总数为2。如果这个数量不为0,那么就会循环打印waiting信息。
  3. 每个任务会进行网络IO,等待IO结束的时候,会触发countDownLatch.countDown();
  4. 直到两个任务都执行完毕,才会停止循环,打印end success

运行结果如下:无限打印2,可见发生了死锁。在这里插入图片描述

值得注意的是:

  1. 虚拟线程发生的死锁,常规的检测工具是检测不出来的。jstackjconsole我都试过了。
  2. 我们只能从结果的现象发现:两个虚拟线程都无法结束,这个循环会永远的进行下去。

2.2 发生原因的推测

而这个打印堆栈的功能和-Djdk.tracePinnedThreads这个VM参数息息相关。

我们全局搜索这个参数:

private static final int TRACE_PINNING_MODE = tracePinningMode();

private static int tracePinningMode() {
   String propValue = GetPropertyAction.privilegedGetProperty("jdk.tracePinnedThreads");
   if (propValue != null) {
       if (propValue.length() == 0 || "full".equalsIgnoreCase(propValue))
           return 1;
       if ("short".equalsIgnoreCase(propValue))
           return 2;
   }
   return 0;
}

可以看到,只要设置了这个参数,这个返回值就是大于0的。还记得我在 虚拟线程详解 这篇文章里面提到的VThreadContinuation吗。那么我们再看看虚拟线程底层对Continuation的封装:这里面重写了一个onPinned函数,也就是说,发生pinned的时候,打印相关的堆栈信息

private static class VThreadContinuation extends Continuation {
    VThreadContinuation(VirtualThread vthread, Runnable task) {
        super(VTHREAD_SCOPE, () -> vthread.run(task));
    }
    @Override
    protected void onPinned(Continuation.Pinned reason) {
        if (TRACE_PINNING_MODE > 0) {
            boolean printAll = (TRACE_PINNING_MODE == 1);
            PinnedThreadPrinter.printStackTrace(System.out, printAll);
        }
    }
}

我们往下跟进:

static void printStackTrace(PrintStream out, boolean printAll) {
    List<LiveStackFrame> stack = STACK_WALKER.walk(s ->
        s.map(f -> (LiveStackFrame) f)
                .filter(f -> f.getDeclaringClass() != PinnedThreadPrinter.class)
                .collect(Collectors.toList())
    );

    // find the closest frame that is causing the thread to be pinned
    stack.stream()
        .filter(f -> (f.isNativeMethod() || f.getMonitors().length > 0))
        .map(LiveStackFrame::getDeclaringClass)
        .findFirst()
        .ifPresent(klass -> {
            int hash = hash(stack);
            Hashes hashes = HASHES.get(klass);
            synchronized (hashes) {
                // print the stack trace if not already seen
                if (hashes.add(hash)) {
                    printStackTrace(stack, out, printAll);
                }
            }
        });
}
private static void printStackTrace(List<LiveStackFrame> stack,
                                        PrintStream out,
                                        boolean printAll) {
    out.println(Thread.currentThread());
    for (LiveStackFrame frame : stack) {
        var ste = frame.toStackTraceElement();
        int monitorCount = frame.getMonitors().length;
        if (monitorCount > 0 || frame.isNativeMethod()) {
            out.format("    %s <== monitors:%d%n", ste, monitorCount);
        } else if (printAll) {
            out.format("    %s%n", ste);
        }
    }
}

看到没,上面有一个synchronized 关键字,里面的代码也是一个IO打印。

  1. 结合上下文来看,我们知道,在虚拟线程中,如果有IO阻塞,那么Loom会调用park()进行yield调用。
  2. 我们假设虚拟线程A抢到了锁。然后调用了IO相关的函数,因此进入yield(第一点)。
  3. 而众所周知,调用yield是不会释放锁的。那么虚拟线程B抢不到锁,由于synchronized关键字的作用,状态进入pinned。导致无法卸载,固定在运载线程。
  4. 那么运载线程被占用,卡在这,所以程序永远无法执行完毕。

最后,如果把下面的这行代码:

VIRTUAL_THREAD_COUNT = PLATFORM_THREAD_COUNT + 1;

替换成:

VIRTUAL_THREAD_COUNT = PLATFORM_THREAD_COUNT;

就不会产生死锁的情况了
在这里插入图片描述
执行结果如下:
在这里插入图片描述

2.3 总结

进行虚拟线程的代码改造的时候,我们要注意一个点:

  1. synchronized关键字对虚拟线程pinned的副作用,我们要考虑到如何兼容和更改,可以使用重入锁进行替代。
  2. 由于这个synchronized关键字我们难以排查完全,我们可以增加-Djdk.tracePinnedThreads参数信息打印pinned发生时候的堆栈信息,助于我们排查,但是这个操作建议只在本地或者测试环境进行。因为他可能会导致你的程序发生死锁。
  3. 建议测试环境进行灰度测试,保证pinned的情况不再发生的时候,可以再发到生产环境进行灰度。

最后的最后,附上堆栈信息的获取方式:

  1. 输入命令jps,找到你自己运行程序的pid
  2. 输入jstack命令:jstack -l 你自己的pid > 1.txt。这样就可以在这个文本中看到发生死锁时候的堆栈信息了。

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

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

相关文章

Protues 仿真报错Internal Exception: access violation in module ‘UNKNOWN‘[7ADEEEA9]

在使用STM32F103C8进行Protues仿真设计的时候&#xff0c;出现了这个报错&#xff0c;通过查找和定位问题&#xff0c;发现是我在配置供电网络的时候配置错误&#xff0c;要配置成如下&#xff1a; 至于为什么回这样&#xff0c;我猜想应该是和这个软件导入STM32芯片的时候&…

300个智商测试FLASH智商游戏ACCESS数据库

最近在找IQ测试方面的数据&#xff0c;网上大多只留传着33道题这种类型&#xff0c;其他的又因各种条件&#xff08;比如图片含水印等&#xff09;不能弄&#xff0c;这是从测智网下载的一些测试智商的游戏数据&#xff0c;游戏文件是FLASH的&#xff0c;扩展名是SWF。 数据包总…

FineReport主题组件使用

主题 添加主题 服务器-》模版主题管理&#xff0c;设置决策报表与普通报表的模版主题&#xff1a; 修改内置模版&#xff0c;打开&#xff0c;点击另存为设置自己的主题名称&#xff0c;保存主题 根据自己需求设置模版相关样式&#xff1a;模版背景、单元格样式、图表样式、…

模板方法设计模式(C++)

定义 定义一个操作中的算法的骨架(稳定&#xff09;,而将一些步骤延迟(变化&#xff09;到子类中。Template Method使得子类可以不改变(复用&#xff09;一个算法的结构即可重定义(override重写)该算法的某些特定步骤。 ——《设计模式》GoF Template Method模式是一种非常基…

转录组下游分析 | 懒人分析推荐

写在前面 今天在GitHub看到一个博主写的RNASeqTool的ShinApp&#xff0c;里面包含了PCA、DESeq2、volcano、NormEnrich、GSEA、Gene tred analysis和WGCNA分析。使用后还是很方便的&#xff0c;就此推荐给大家。感兴趣可以自己操作即可。 GitHub网址 https://github.com/Cha…

QT以管理员身份运行

以下配置后&#xff0c;QT在QT Creator调试时&#xff0c;或者生成的.exe程序&#xff0c;都将会默认以管理员身份运行。 一、MSVC编译器 1、在Pro文件中添加以下代码&#xff1a; QMAKE_LFLAGS /MANIFESTUAC:\"level\requireAdministrator\ uiAccess\false\\" …

Java 8 中使用 Stream 遍历树形结构

在实际开发中&#xff0c;我们经常会开发菜单&#xff0c;树形结构&#xff0c;数据库一般就使用父id来表示&#xff0c;为了降低数据库的查询压力&#xff0c;我们可以使用Java8中的Stream流一次性把数据查出来&#xff0c;然后通过流式处理&#xff0c;我们一起来看看&#x…

利用openTCS实现车辆调度系统(二)openTCS下载部署

openTCS下载 **openTCS不是一个部署即用的系统&#xff0c;需要自己做一部分二次开发工作。这里先下载打包好的应用&#xff0c;了解openTCS的功能&#xff1a;应用下载地址**整个部署使用部分包括&#xff0c;启动程序、应用地图、准备设备、下发订单。 1、启动调度内核 双…

idea中cherry pick怎么用

网上的文章比较杂乱&#xff0c;具体idea里面怎么操作&#xff0c;好像没有个成型的文章 cherry pick的作用&#xff1a; 简而言之就是同一个commit&#xff0c;然后复制提交到其他分支&#xff08;只检出本次提交的&#xff09;&#xff1b; 解决的问题&#xff1a; 两个分…

轻松搭建酒店小程序

酒店小程序的制作并不需要编程经验&#xff0c;只需要按照以下步骤进行操作&#xff0c;就能很快地搭建自己的小程序商城。 第一步&#xff0c;注册登录账号进入操作后台&#xff0c;找到并点击【商城】中的【去管理】进入商城的后台管理页面&#xff0c;然后再点击【小程序商城…

原型模式与享元模式:提升系统性能的利器

原型模式和享元模式&#xff0c;前者是在创建多个实例时&#xff0c;对创建过程的性能进行调优&#xff1b;后者是用减 少创建实例的方式&#xff0c;来调优系统性能。这么看&#xff0c;你会不会觉得两个模式有点相互矛盾呢&#xff1f; 在有些场景下&#xff0c;我们需要重复…

Linux安装cuda和cudnn教程

Linux安装cuda和cudnn教程 文章目录 1.下载cuda和cudnn2. 安装cuda并检验安装是否成功3. 安装cudnn4.验证cuda是否能用代码附件&#xff1a;解压各种格式文件的Linux命令参考文献 卸载之前的cuda 卸载之前的cuda教程 1.下载cuda和cudnn CUDA下载地址&#xff1a;https://dev…

Tailwind CSS:简洁高效的工具,提升前端开发体验

112. Tailwind CSS&#xff1a;简洁高效的工具&#xff0c;提升前端开发体验 1. 什么是Tailwind CSS&#xff1f; Tailwind CSS是由Adam Wathan、Jonathan Reinink、David Hemphill和Steve Schoger等人共同创建的一种现代CSS框架。与传统的CSS框架不同&#xff0c;Tailwind CS…

如何用 NPS 确定研发优先级,打破技术与业务的次元壁?

「不了解利益相关者的需求是僵尸 Scrum 团队的四大常见症状之一&#xff0c;其主要表现为成员们忽视价值链上下游的内容&#xff0c;无法或不愿意带来任何改变或影响」&#xff0c;《拯救僵尸 Scrum》如是写道。 它们的工作&#xff0c;以及工作所涉及的组织&#xff0c;往往被…

Spark、RDD、Hive 、Hadoop-Hive 和传统关系型数据库区别

Hive Hadoop Hive 和传统关系型数据库区别 Spark 概念 基于内存的分布式计算框架 只负责算 不负责存 spark 在离线计算 功能上 类似于mapreduce的作用 MapReduce的缺点 运行速度慢 &#xff08;没有充分利用内存&#xff09;接口比较简单&#xff0c;仅支持Map Reduce功能…

AI写作宝有哪些,分享两种AI写作工具

AI写作宝是一种基于人工智能技术的写作辅助工具。它可以根据用户输入的关键词和主题快速生成文章。AI写作宝可以为用户节省大量的时间和精力&#xff0c;帮助用户快速生成高质量的文章。今天就为大家推荐两款AI写作宝&#xff1a; 一、AI创作家 AI创作家是一款基于人工智能技…

在java中存储对象到redis出现类型转换异常的解决方法

**出现的问题,**此时的redisCatch已经注入 原因:这里传进来的是一个对象,redis不能直接将对象存到String中,必须将对象进行序列化转成json字符串再存储,其次.传进来的对象不能是null 再重新启动就行了

整会promise这8个高级用法,再被问倒来喷我

前端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 发现很多人还只会promise常规用法 在js项目中&#xff0c;promise的使用应该是必不可少的&#xff0c;但我发现在同事和面试者中&#xff0c;很多中级或…

GD32F103VE睡眠与唤醒

GD32F103VE睡眠与唤醒&#xff0c;兆易官网给的程序没有测试。等测试后&#xff0c;才发现有问题。 现修改&#xff0c;测试如下&#xff1a; #include "SleepMode.h" #include "delay.h"u8 WFE_CMD_EnterSleepModeFlag;void Enter_DeepSleepMode(void);…

关于计算机大学生秋招面试的那点事?(Golang篇)

前言&#xff1b; Go语言&#xff08;简称Golang&#xff09;越来越受到开发者的关注和欢迎。它由Google公司于2009年推出&#xff0c;旨在提供更好的性能和并发性能。眼下&#xff0c;越来越多的公司在使用它&#xff0c;比如著名的云计算服务商AWS&#xff0c;以及知名电商京…