当前位置: 首页 > news >正文

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

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:sspublic 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 clientclient = 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));}@Overrideprotected 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 pinnedstack.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 seenif (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。这样就可以在这个文本中看到发生死锁时候的堆栈信息了。

相关文章:

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

JDK19 - synchronized关键字导致的虚拟线程PINNED 前言一. PINNED是什么意思1.1 synchronized 绑定测试1.2 synchronized 关键字的替代 二. -Djdk.tracePinnedThreads的作用和坑2.1 死锁案例测试2.2 发生原因的推测2.3 总结 前言 在 虚拟线程详解 这篇文章里面&#xff0c;我们…...

用msys2安装verilator并用spinal进行仿真

一 参考 SpinalHDL 开发环境搭建一步到位(图文版) - 极术社区 - 连接开发者与智能计算生态 (aijishu.com)https://aijishu.com/a/1060000000255643Setup and installation of Verilator — SpinalHDL documentation...

【ARM64 常见汇编指令学习 13 -- ARM 汇编 ORG 伪指令学习】

文章目录 ARM ORG 指令介绍UEFI 中对 ORG 指令的使用 ARM ORG 指令介绍 在ARM汇编中&#xff0c;"org"是一个汇编器伪指令&#xff0c;用于设置下一条指令的装入地址。"org"后面跟着的是一个表达式&#xff0c;这个表达式的值就是下一条指令的装入地址。如…...

Vue使用QuillEditor富文本编辑器问题记录

1.内容绑定的问题 绑定内容要使用 v-model:content"xxx" 的形式。 2.设置字体字号 字体以及字号大小的设置需要先注册。 <script> import { QuillEditor,Quill } from vueup/vue-quill import vueup/vue-quill/dist/vue-quill.snow.css; // 设置字体大小 c…...

spring AOP学习

概念 面向切面编程横向扩展动态代理 相关术语 动态代理 spring在运行期&#xff0c;生成动态代理对象&#xff0c;不需要特殊的编译器 Spring AOP的底层就是通过JDK动态代理或者CGLIb动态代理技术为目标Bean执行横向织入 目标对象实现了接口&#xff0c;spring使用JDK的ja…...

16.M端事件和JS插件

16.1移动端 移动端也有自己独特的地方 ●触屏事件touch (也称触摸事件)&#xff0c;Android 和I0S都有。 ●touch对象代表一个触摸点。触摸点可能是一根手指&#xff0c;也可能是一根触摸笔。触屏事件可响应用户手指(或触控笔)对屏幕或者触控板操作。 ●常见的触屏事件如下: …...

Zebec APP:构建全面、广泛的流支付应用体系

目前&#xff0c;流支付协议 Zebec Protocol 基本明确了生态的整体轮廓&#xff0c;它包括由其社区推动的模块化 Layer3 构架的公链 Nautilus Chain、流支付应用 Zebec APP 以及 流支付薪酬工具 Zebec payroll 。其中&#xff0c;Zebec APP 是原有 Zebec Protocol 的主要部分&a…...

Spark 3.1.1 遇到的 from_json regexp_replace组合表达式慢问题的解决

背景 目前公司在从spark 2.4.x升级到3.1.1的时候&#xff0c;遇到了一类SQL极慢的情况&#xff0c;该SQL的如下(只列举了关键的)&#xff1a; select device_personas.* from(selectdevice_id, ads_id, from_json(regexp_replace(device_personas, (?<(\\{|,))"devic…...

Docker 容器常用的命令和操作

1.容器操作 - 运行容器: docker run [OPTIONS] IMAGE [COMMAND] [ARG...] 示例&#xff1a; docker run -it --rm ubuntu /bin/bash - 查看正在运行的容器: docker ps [OPTIONS] 示例&#xff1a; docker ps -a - 停止容器: docker stop CONTAINER [CONTAINER...] 示…...

iTOP-RK3568开发板Windows 安装 RKTool 驱动

在烧写镜像之前首先需要安装 RKTool 驱动。 RKTool 驱动在网盘资料“iTOP-3568 开发板\01_【iTOP-RK3568 开发板】基础资料 \02_iTOP-RK3568 开发板烧写工具及驱动”路径下。 驱动如下图所示&#xff1a; 解压缩后&#xff0c;进入文件夹&#xff0c;如下图所示&#xff1a;…...

nginx rtmp http_flv直播推流

安装配置nginx yum install epel-release -y sudo rpm -Uvh http://li.nux.ro/download/nux/dextop/el7/x86_64/nux-dextop-release-0-5.el7.nux.noarch.rpm yum install ffmpeg ffmpeg-devel -y yum install gcc -y yum install pcre pcre-devel -y yum install openssl open…...

Day50 算法记录| 动态规划 17(子序列)

这里写目录标题 647. 回文子串516.最长回文子序列总结 647. 回文子串 1.动态规划和2.中心扩展 这个视频是基于上面的视频的代码 方法1:动态规划 布尔类型的dp[i][j]&#xff1a;表示区间范围[i,j] &#xff08;注意是左闭右闭&#xff09;的子串是否是回文子串&#xff0c;如…...

RabbitMQ:概念和安装,简单模式,工作,发布确认,交换机,死信队列,延迟队列,发布确认高级,其它知识,集群

1. 消息队列 1.0 课程介绍 1.1.MQ 的相关概念 1.1.1.什么是MQ MQ(message queue&#xff1a;消息队列)&#xff0c;从字面意思上看&#xff0c;本质是个队列&#xff0c;FIFO 先入先出&#xff0c;只不过队列中存放的内容是message 而已&#xff0c;还是一种跨进程的通信机制…...

小研究 - 基于解析树的 Java Web 灰盒模糊测试(二)

由于 Java Web 应用业务场景复杂, 且对输入数据的结构有效性要求较高, 现有的测试方法和工具在测试Java Web 时存在测试用例的有效率较低的问题. 为了解决上述问题, 本文提出了基于解析树的 Java Web 应用灰盒模糊测试方法. 首先为 Java Web 应用程序的输入数据包进行语法建模创…...

对于现有的分布式id发号器的思考 id生成器 雪花算法 uuid

在工作过程中接触了很多id生成策略&#xff0c;但是有一些问题 雪花id 强依赖时钟&#xff0c;对于时钟回拨无法很好解决 tinyid 滴滴开源&#xff0c;依赖mysql数据库&#xff0c;自增&#xff0c;无业务属性 uuid 生成是一个字符串没有顺序&#xff0c;数据库索引组织数据…...

jmeter中json提取器,获取多个值,并通过beanshell组成数组

jmeter中json提取器介绍 特别说明&#xff1a;**Compute concatenation var(suffix_ALL)&#x1f617;*如果找到许多结果&#xff0c;则插件将使用’ &#xff0c; 分隔符将它们连接起来&#xff0c;并将其存储在名为 _ALL的var中 json提取器调试 在查看结果树中选择JSON Pat…...

通过nvm工具快捷切换node.js版本、以及nvm的安装

使用nvm可以实现多个Node.js版本之间切换 步骤目录&#xff1a; 先卸载掉本系统中原有的node版本 去github上下载nvm安装包 安装node 常用的一些nvm命令 1、先卸载掉本系统中原有的node版本 2、去github上下载nvm安装包 https://github.com/coreybutler/nvm-windows/re…...

企业如何搭建矩阵内容,才能真正实现目的?

当下&#xff0c;新媒体矩阵营销已成为众多企业的营销选择之一&#xff0c;各企业可以通过新媒体矩阵实现扩大品牌声量、维持用户关系、提高销售业绩等不同的目的。 而不同目的的矩阵&#xff0c;它的内容运营模式会稍有差别&#xff0c;评价体系也会大不相同。 企业在运营某类…...

Arduino驱动MQ5模拟煤气气体传感器(气体传感器篇)

目录 1、传感器特性 2、硬件原理图 3、驱动程序 MQ5气体传感器,可以很灵敏的检测到空气中的液化气、天然气、煤气等气体,与Arduino结合使用,可以制作火灾液化气、天然气、煤气泄露报警等相关的作品。 1、传感器特性 MQ5用于消费和工业行业中气体泄漏检测设备,该传感器适…...

Mongodb安装(Centos7)

1. 下载 MongoDB: The Developer Data Platform | MongoDB 2. 安装 上传至服务器 解压 tar -zxvf mongodb-linux-x86_64-rhel70-5.0.19.tgz 移动 mv mongodb-linux-x86_64-rhel70-5.0.19 /usr/local/mongodb 3. 配置 vim /etc/profile # set mongodb configuration expor…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(十一)

下载buildroot https://buildroot.org/download.html下载交叉工具链 使用ST官方交叉工具链的话&#xff0c;在buildroot配置外部工具会有问题&#xff0c;所以直接使用正点原子的交叉编译工具 buildroot构建根文件系统 - 参考正点原子 配置 buildroot tar -vxf buildroot-20…...

国产高云FPGA实现视频采集转UDP以太网输出,FPGA网络摄像头方案,提供2套Gowin工程源码和技术支持

目录 1、前言工程概述免责声明 2、相关方案推荐我已有的所有工程源码总目录----方便你快速找到自己喜欢的项目国产高云FPGA基础教程国产高云FPGA相关方案推荐我这里已有的以太网方案 3、设计思路框架工程设计原理框图输入Sensor之-->OV7725摄像头输入Sensor之-->OV5640摄…...

MCP(Model Context Protocol)与提示词撰写

随着大模型&#xff08;LLM&#xff09;在复杂任务中的普及&#xff0c;如何让模型高效调用外部工具和数据成为关键挑战。传统函数调用&#xff08;Function Calling&#xff09;依赖开发者手动封装 API&#xff0c;而 MCP&#xff08;Model Context Protocol&#xff09; 通过…...

【联网玩具】EN 18031欧盟网络安全认证

在当今数字化时代&#xff0c;带联网功能的玩具越来越受到孩子们的喜爱&#xff0c;它们为儿童带来了前所未有的互动体验和学习机会。然而&#xff0c;随着这类玩具的普及&#xff0c;网络安全问题也日益凸显。为了保障儿童使用这类玩具时的安全与隐私&#xff0c;欧盟出台了 E…...

csharp基础....

int[][] jaggedArray new int[3][]; jaggedArray[0] new int[] { 1, 2 }; jaggedArray[1] new int[] { 3, 4, 5 }; jaggedArray[2] new int[] { 6, 7, 8, 9 }; 嵌套 反转和排序 List<int> list new List<int> { 1, 2, 3, 4, 5 }; list.Reverse(); Cons…...

解决pycharm同一个文件夹下from *** import***仍显示No module named

1、&#xff0c;from ***import *&#xff0c;同文件夹中已有.py文件但是仍然报错No module named 原因是因为pycharm没有把文件夹设置为根目录&#xff0c;只需要在文件夹的上一级设置为根目录即可&#xff0c;测试过如果仅仅将当前的文件夹设置仍然报错&#xff0c;如果把最上…...

Python60日基础学习打卡Day46

一、 什么是注意力 注意力机制的由来本质是从onehot-elmo-selfattention-encoder-bert这就是一条不断提取特征的路。各有各的特点&#xff0c;也可以说由弱到强。 其中注意力机制是一种让模型学会「选择性关注重要信息」的特征提取器&#xff0c;就像人类视觉会自动忽略背景&…...

当SAP系统内计划订单转换为生产订单时发生了什么?

【SAP系统研究】 #SAP #计划订单 #生产订单 #采购申请 一、关于计划订单的一点疑惑 曾经对SAP为什么会有计划订单,是感到很疑惑的。 这个界面简单,配置点也不多,能被随意“摆布”,一旦要变形就消失得无影无踪的计划订单,why? 但是,再次重新审视过之后,才发现它其实…...

图上合成:用于大型语言模型持续预训练的知识合成数据生成

摘要 大型语言模型&#xff08;LLM&#xff09;已经取得了显著的成功&#xff0c;但仍然是数据效率低下&#xff0c;特别是当学习小型&#xff0c;专业语料库与有限的专有数据。现有的用于连续预训练的合成数据生成方法集中于文档内内容&#xff0c;而忽略了跨文档的知识关联&a…...

船舶事故海上搜救VR情景演练全场景 “复刻”,沉浸式救援体验​

船舶事故海上搜救 VR 情景演练系统的一大核心优势&#xff0c;便是能够全场景 “复刻” 海上事故&#xff0c;为使用者带来沉浸式的船舶事故海上搜救 VR 情景演练体验。​ 在船舶事故海上搜救 VR 情景演练的事故场景模拟方面&#xff0c;系统几乎涵盖了所有常见的船舶事故类型。…...