多线程我太爱你了,你个“大可爱”
又是大早上被多线程折磨的一天
先简单讲一下功能框架吧
flowchart TD
A(用户发起查询操作) --> B{查询cache}
B -- 有 --> C[返回cache内容给用户]
B -- 没有 --> D[请求remote]
D --> E[remote返回内容]
E --> F[内容返回给用户]
E --> G[remote内容写入cache]
简单来说就是从两个数据源获取信息,先查 cache,cache 没有再查 remote,remote 得到数据后返回给用户并写入 cache
然后就出问题了,具体问题表现为出现重复插入情况
经过排查,发现 remote 请求返回的数据正常,但是到写入 Room 数据库的时候出现插入数量正确,但是部分 item 重复插入,部分 item 丢失的情况
问了一下 ChatGPT,在给出的几个可能性中锁定到多线程并发写入导致的竞态和异常。
不过虽然该保存数据的函数确实是 suspend 的异步函数,但是我在函数入口明明打了断点,断点只触发一次,不存在多线程的问题
把代码贴上来问了问 ChatGPT,结果给我整绷不住了:
不是,也没人给我说呀,怎么 kotlin 的 foreach 和 for 还有这大坑等着我的。。。
然而把 foreach 改成 for 后还是一样的问题,继续追问
在众多的可能性中,锁定协程作用域问题
指出 确保你的suspend fun saveServiceList()不是在多个协程并发调用!否则即使方法内部串行,外部还是会并发插入,导致竞态。
但是之前说过,我打的断点明明只触发一次,怎么会出现外部多次调用呢?
没办法,按照 ChatGPT 说的老老实实打 Log 看输出,在 dao 的 insert 方法前后插 Log,发现确实是出现竟态问题。
然后 ChatGPT 再次指出确保整个 suspend 方法不在多个协程并发调用,提出用 Mutex 锁包裹整个 suspend 方法
结果发现居然没问题了???
所以还真是被多个协程调用了???我怎么不知道???
虽然用锁临时解决了该问题,但这个操作本质是不应该被多个协程调用的,说明代码本身有问题
本着不想写屎山的心态,继续问
然后 ChatGPT 指出:
所以断点在并发协程中并不适用来判断调用次数
我:多线程你太可爱了
给 suspend 方法第一行插个 Log,发现确实日志打印了两遍
然后就是顺藤摸瓜,给所有调用点全部插 Log,最后发现在 ViewModel 中的起始 loadService 函数被调用了两次
定位到 View 中发现在 UI 里有两处调用,一看代码就发现问题了。
一个来自 ViewModel 中的 loadServiceState 在 View 中监听,当 State 由 Idle 变为 Loading 后触发 loadService,加载数据
然后一个手动刷新按钮,首先清除数据,然后更改 loadServiceState 的 State 为 Loading,最后手动调用 loadService
因此当手动刷新时,就会出现 loadServiceState 改变而触发的 loadService 以及按钮自身调用的 loadService。。。
代码一多自己都忘记原来写的逻辑了,又是被多线程折腾的一天
Use this card to join MyBlog and participate in a pleasant discussion together .
Welcome to GoodBoyboy 's Blog,wish you a nice day .