有没有 vert.x 或者有关注 Java 虚拟线程的群

2023-05-03 09:45:57 +08:00
 byte10

一、背景

最近使用 vert.x 在开发一个 java 程序,只所以用 vert.x 就是因为它比较适合开发这类的程序。但是异步编程开发起来实在不好维护。。实现也太麻烦了,实在是太复杂了。下单完还要判断是否成功,还有循环下单等,很多时候异步实现起来非常的不好实现和阅读。

二、引入虚拟线程

目前引入的虚拟线程遇到非常大的麻烦。一般一个 verticle 绑定一个 eventloop 线程,我把虚拟线程绑定 vert.x eventloop 的线程中,,这样每个 verticle 内使用的所有虚拟线程也是它们自己平台 eventloop 线程,也就是在 verticle 使用的所有虚拟线程和自己平台线程 都是同一个线程,所以理论也是线程安全的。目前遇到的是虚拟线程和它自己的平台线程在执行 log 日志输出的时候,就会遇到死锁。也就是虚拟线程和它自己的平台线程发生了竞争 log.info 的输出。

下面是异常日志:

Thread Thread[#63,vert.x-eventloop-thread-1,5,main] has been blocked for 10586 ms, time limit is 2000 ms
io.vertx.core.VertxException: Thread blocked
        at java.base/jdk.internal.misc.Unsafe.park(Native Method)
        at java.base/java.util.concurrent.locks.LockSupport.park(LockSupport.java:221)
        at java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:754)
        at java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:990)
        at java.base/java.util.concurrent.locks.ReentrantLock$Sync.lock(ReentrantLock.java:153)
        at java.base/java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:322)
        at ch.qos.logback.core.OutputStreamAppender.writeBytes(OutputStreamAppender.java:200)
        at ch.qos.logback.core.OutputStreamAppender.writeOut(OutputStreamAppender.java:193)
        at ch.qos.logback.core.OutputStreamAppender.subAppend(OutputStreamAppender.java:228)
        at ch.qos.logback.core.OutputStreamAppender.append(OutputStreamAppender.java:102)
        at ch.qos.logback.core.UnsynchronizedAppenderBase.doAppend(UnsynchronizedAppenderBase.java:85)
        at ch.qos.logback.core.spi.AppenderAttachableImpl.appendLoopOnAppenders(AppenderAttachableImpl.java:51)
        at ch.qos.logback.classic.Logger.appendLoopOnAppenders(Logger.java:272)
        at ch.qos.logback.classic.Logger.callAppenders(Logger.java:259)
        at ch.qos.logback.classic.Logger.buildLoggingEventAndAppend(Logger.java:426)

使用 jstack -l pid 检查确实一直 block 中.

LockSupport.class

public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        try {
            if (t.isVirtual()) {
                VirtualThreads.park();
            } else {
                U.park(false, 0L);
            }
        } finally {
            setBlocker(t, null);
        }
    }

由于 verticle 可能是很多个,可能会绑定到同一个 eventloop 线程,所以很难从代码上比较难规避不使用 eventloop 平台线程 。

一般使用 ReentrantLock.lock 产生死锁的情况是什么原因呢?个人感觉跟 vert.x 关系不大,应该跟虚拟线程的有关系。

目前不确定是否 ReentrantLock.lock 的问题,还是 logback 日志框架的问题。尝试直接用 ReentrantLock.lock 进行测试,额-也没有复现。。。但是用 log.info 打印日志必现。

2981 次点击
所在节点    Java
30 条回复
byte10
2023-05-03 19:23:28 +08:00
@leatomic 好家伙这个调试 也是大问题,看来还是要虚拟线程才行,,不然一直堵塞 eventloop 的 代码。。还是引入虚拟线程比较好调试。
yazinnnn
2023-05-03 19:30:04 +08:00
1. 如果你在 vertx 里用锁去同步代码, 很可能你的 vertx 使用方式有问题
2. 如果是回调嵌套太深的话, 用 future/promise 去打平代码逻辑,或者用 kotlin 协程去打平, 回调 api 在 vertx5 中会废弃
3. vertx 这种基于 netty 的 nio 框架还是更适合 kotlin 协程, loom 更适合传统 bio 框架
4. 如果你的业务稍微有些复杂度的话,建议上 quarkus
D3EP
2023-05-03 19:32:09 +08:00
这日志不是死锁,而是 EventLoop 因为日志打印被阻塞住了。Logback 配置有缺陷,同步刷盘肯定会影响 EventLoop ,改成 AsyncAppender 就好了。
lixintcwdsg
2023-05-03 20:27:54 +08:00
@leatomic 我给楼主建议的就是互补,业务逻辑用虚拟线程,楼主硬要融合嘛不是,这本就是一件事的两个解决方案。
lixintcwdsg
2023-05-03 20:36:28 +08:00
@byte10
大家对 eventloop 的理解是不是有误会。
netty 类型 eventloop 的线程安全是针对单个连接的,实际上除了游戏和 IM 服务器一根 TCP 长连接直连 netty-base 的服务器(其实这比较罕见,因为没经过网关),其余情况下要么是 eventloop 单线程跑,要不根本谈不上线程安全。
对于 http 短链接来说,你用 eventloop 下的某一个 loop 的线程跑和用虚拟线程跑业务都没有线程安全问题,一次连接一个线程。
虚拟线程配合 eventloop ,虚拟线程处理结束要回写 task 给当前 loop 线程的 task 队列。
另,如果要讲究线程安全,数据使用的方式就不要用共享内存的形式。
另,没记错虚拟线程的 JEP 里面明确有讲,不推荐虚拟线程共享变量。
byte10
2023-05-03 20:41:19 +08:00
@D3EP 我确实也是改成了 AsyncAppender 好像也不行,回头我再试试。但是我 手动执行 Lock.lock() 来模拟 log.info ,也不会触发这个 park() 的 bug 。所以问题点 好像也不一定是 线程的 park 导致的,可能是 Logback 的 LockSupport.park()。不过这个日志确实不适合 EventLoop ,我要换 vert.x 自带的日志才好。
@yazinnnn 我并不是要在 vert.x 去锁同步代码。。我目标是需要把异步编程 改成同步编程。另外你说的 vertx5 要去回调 API? 那 直接同步返回吗?
byte10
2023-05-03 20:53:32 +08:00
@lixintcwdsg 嗯 感谢,对于 eventloop 我可能没你理解那么深刻。vert.x 的 Verticle 就是线程安全的,因为它绑定在同一 eventloop 线程中,任何时刻的代码都运行在同一个线程,所以不存在并发安全。我代码贴出的代码示例,有描述我的需求,就是想把异步编程转成同步编程。虚拟线程可以做到。如果使用虚拟线程,那么就会存在线程安全问题,所以我才想把它绑定到 eventloop 的 EventLoop carrier ,这样就可以绑定到同一个 EventLoop 线程了,就不存在线程安全问题了
kaneg
2023-05-04 00:14:57 +08:00
虚拟线程的一个重要影响是取代线程池:因为其创建代价极小,所以意在减少线程创建开销而生的线程池就没有存在的意义了。
至于多线程本身的编程模型,虚拟线程应该还是遵守的。
asssfsdfw
2023-05-04 16:11:45 +08:00
byte10
2023-05-04 16:21:36 +08:00
@asssfsdfw 嗯👌🏻,我了解下这个

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/936958

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX