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
的情况:
- 当调用的代码中被
synchronized
关键字修饰。 - 执行
native method
或foreign 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();}
}
分析:
- 我们有用于测试同步锁的对象
instance
,专门拿来给synchronized
关键字用的。 - 两种执行任务方式:一种普通的,一种加锁的。
- 执行的任务做了什么:睡眠了一秒钟,并且打印相关数据。
- 我们同时启动两个
task
,看看最终的结果是什么。
我们在启动之前,给Main函数添加一些参数:
-
-Djdk.tracePinnedThreads=full
:开启对虚拟线程的跟踪。设置为"full"
表示输出详细的虚拟线程信息,包括线程ID
、状态和执行时间等。这样被pinned
的时候,我们就可以通过打印的信息观察到了 (后面有惊喜) -
-Djdk.virtualThreadScheduler.parallelism=1
:这个参数指定了虚拟线程调度器的并行度。并行度表示同时执行虚拟线程的最大数量。在这里,设置为1表示只允许一个虚拟线程同时执行。 -
-Djdk.virtualThreadScheduler.maxPoolSize=1
:这个参数指定了虚拟线程调度器的最大线程池大小。线程池是用于存放虚拟线程的容器。在这里,设置为1表示线程池的大小为1,即最多只能容纳一个虚拟线程。 -
-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
。而因为task1
被synchronized
关键字修饰,因此线程被pinned
:
我们设置可执行的线程数为2:maxPoolSize=2
,那么此时两个任务可以同时提交,但是task1
被synchronized
关键字修饰,因此线程同样被pinned
:
-Djdk.tracePinnedThreads=full -Djdk.virtualThreadScheduler.parallelism=1 -Djdk.virtualThreadScheduler.maxPoolSize=2 -Djdk.virtualThreadScheduler.minRunnable=1
注意:这两个返回结果的顺序是不一样的!
从上面的结果上来看,直观的结论就是:
- 如果执行代码中包含了
synchronized
关键字,那么这个线程将会被pinned
。即任务1所在的虚拟线程无法卸载,而是被固定到了运载线程。 - 哪怕两个任务是“同时”提交,也会优先将任务1(被
pinned
的线程)执行完毕,再去启动任务2。因为任务2只能等待任务1执行完毕才能够继续执行。 - 那么也就失去了异步的一个概念了。
那么针对这种情况,我们如何解决?官方建议是使用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个,虚拟线程数为2个。然后启动两个虚拟线程任务。
- 启动任务之前,我们初始化了一个栅栏
CountDownLatch
,总数为2。如果这个数量不为0,那么就会循环打印waiting
信息。 - 每个任务会进行网络
IO
,等待IO
结束的时候,会触发countDownLatch.countDown();
- 直到两个任务都执行完毕,才会停止循环,打印
end success
运行结果如下:无限打印2,可见发生了死锁。
值得注意的是:
- 虚拟线程发生的死锁,常规的检测工具是检测不出来的。
jstack
和jconsole
我都试过了。 - 我们只能从结果的现象发现:两个虚拟线程都无法结束,这个循环会永远的进行下去。
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
打印。
- 结合上下文来看,我们知道,在虚拟线程中,如果有
IO
阻塞,那么Loom
会调用park()
进行yield
调用。 - 我们假设虚拟线程A抢到了锁。然后调用了
IO
相关的函数,因此进入yield
(第一点)。 - 而众所周知,调用
yield
是不会释放锁的。那么虚拟线程B抢不到锁,由于synchronized
关键字的作用,状态进入pinned
。导致无法卸载,固定在运载线程。 - 那么运载线程被占用,卡在这,所以程序永远无法执行完毕。
最后,如果把下面的这行代码:
VIRTUAL_THREAD_COUNT = PLATFORM_THREAD_COUNT + 1;
替换成:
VIRTUAL_THREAD_COUNT = PLATFORM_THREAD_COUNT;
就不会产生死锁的情况了
执行结果如下:
2.3 总结
进行虚拟线程的代码改造的时候,我们要注意一个点:
synchronized
关键字对虚拟线程pinned
的副作用,我们要考虑到如何兼容和更改,可以使用重入锁进行替代。- 由于这个
synchronized
关键字我们难以排查完全,我们可以增加-Djdk.tracePinnedThreads
参数信息打印pinned
发生时候的堆栈信息,助于我们排查,但是这个操作建议只在本地或者测试环境进行。因为他可能会导致你的程序发生死锁。 - 建议测试环境进行灰度测试,保证
pinned
的情况不再发生的时候,可以再发到生产环境进行灰度。
最后的最后,附上堆栈信息的获取方式:
- 输入命令
jps
,找到你自己运行程序的pid
。 - 输入
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 总结 前言 在 虚拟线程详解 这篇文章里面,我们…...

用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汇编中,"org"是一个汇编器伪指令,用于设置下一条指令的装入地址。"org"后面跟着的是一个表达式,这个表达式的值就是下一条指令的装入地址。如…...

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在运行期,生成动态代理对象,不需要特殊的编译器 Spring AOP的底层就是通过JDK动态代理或者CGLIb动态代理技术为目标Bean执行横向织入 目标对象实现了接口,spring使用JDK的ja…...

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

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

Spark 3.1.1 遇到的 from_json regexp_replace组合表达式慢问题的解决
背景 目前公司在从spark 2.4.x升级到3.1.1的时候,遇到了一类SQL极慢的情况,该SQL的如下(只列举了关键的): select device_personas.* from(selectdevice_id, ads_id, from_json(regexp_replace(device_personas, (?<(\\{|,))"devic…...
Docker 容器常用的命令和操作
1.容器操作 - 运行容器: docker run [OPTIONS] IMAGE [COMMAND] [ARG...] 示例: docker run -it --rm ubuntu /bin/bash - 查看正在运行的容器: docker ps [OPTIONS] 示例: docker ps -a - 停止容器: docker stop CONTAINER [CONTAINER...] 示…...

iTOP-RK3568开发板Windows 安装 RKTool 驱动
在烧写镜像之前首先需要安装 RKTool 驱动。 RKTool 驱动在网盘资料“iTOP-3568 开发板\01_【iTOP-RK3568 开发板】基础资料 \02_iTOP-RK3568 开发板烧写工具及驱动”路径下。 驱动如下图所示: 解压缩后,进入文件夹,如下图所示:…...
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]:表示区间范围[i,j] (注意是左闭右闭)的子串是否是回文子串,如…...

RabbitMQ:概念和安装,简单模式,工作,发布确认,交换机,死信队列,延迟队列,发布确认高级,其它知识,集群
1. 消息队列 1.0 课程介绍 1.1.MQ 的相关概念 1.1.1.什么是MQ MQ(message queue:消息队列),从字面意思上看,本质是个队列,FIFO 先入先出,只不过队列中存放的内容是message 而已,还是一种跨进程的通信机制…...

小研究 - 基于解析树的 Java Web 灰盒模糊测试(二)
由于 Java Web 应用业务场景复杂, 且对输入数据的结构有效性要求较高, 现有的测试方法和工具在测试Java Web 时存在测试用例的有效率较低的问题. 为了解决上述问题, 本文提出了基于解析树的 Java Web 应用灰盒模糊测试方法. 首先为 Java Web 应用程序的输入数据包进行语法建模创…...
对于现有的分布式id发号器的思考 id生成器 雪花算法 uuid
在工作过程中接触了很多id生成策略,但是有一些问题 雪花id 强依赖时钟,对于时钟回拨无法很好解决 tinyid 滴滴开源,依赖mysql数据库,自增,无业务属性 uuid 生成是一个字符串没有顺序,数据库索引组织数据…...

jmeter中json提取器,获取多个值,并通过beanshell组成数组
jmeter中json提取器介绍 特别说明:**Compute concatenation var(suffix_ALL)😗*如果找到许多结果,则插件将使用’ , 分隔符将它们连接起来,并将其存储在名为 _ALL的var中 json提取器调试 在查看结果树中选择JSON Pat…...

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

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

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…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...
OpenLayers 可视化之热力图
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 热力图(Heatmap)又叫热点图,是一种通过特殊高亮显示事物密度分布、变化趋势的数据可视化技术。采用颜色的深浅来显示…...
ES6从入门到精通:前言
ES6简介 ES6(ECMAScript 2015)是JavaScript语言的重大更新,引入了许多新特性,包括语法糖、新数据类型、模块化支持等,显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var…...
树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频
使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...

YSYX学习记录(八)
C语言,练习0: 先创建一个文件夹,我用的是物理机: 安装build-essential 练习1: 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后…...
Rust 异步编程
Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...
【C语言练习】080. 使用C语言实现简单的数据库操作
080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...

华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建
华为云FlexusDeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色,华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型,能助力我们轻松驾驭 DeepSeek-V3/R1,本文中将分享如何…...
Angular微前端架构:Module Federation + ngx-build-plus (Webpack)
以下是一个完整的 Angular 微前端示例,其中使用的是 Module Federation 和 npx-build-plus 实现了主应用(Shell)与子应用(Remote)的集成。 🛠️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...

华硕a豆14 Air香氛版,美学与科技的馨香融合
在快节奏的现代生活中,我们渴望一个能激发创想、愉悦感官的工作与生活伙伴,它不仅是冰冷的科技工具,更能触动我们内心深处的细腻情感。正是在这样的期许下,华硕a豆14 Air香氛版翩然而至,它以一种前所未有的方式&#x…...