基于LiteDB的C#服务器AsyncReaderWriterLock设计合理性问询
问题分析与解决方案
问题重现
你用C#开发服务器,基于LiteDB存储消息,启动异步GC任务删除过期消息:
- 依赖LiteDB的线程安全特性,未给常规读写加锁,仅用
AsyncReaderWriterLock同步GC(写锁)与常规读写(读锁) - 50客户端测试正常,但100客户端时,GC无法获取写锁,最终触发LiteDB超时异常:
Exception thrown: 'LiteDB.LiteException' in LiteDB.dll
Database lock timeout when entering in transaction mode after 00:01:00
核心原因
这不是死锁,是锁饥饿+双层锁机制冲突:
- LiteDB自身已经实现了线程安全的锁与事务机制,你额外套的
AsyncReaderWriterLock属于画蛇添足,两层锁互相干扰。 - 读写锁的特性是读优先:当大量客户端持续持有读锁时,新的读请求可以直接插队获取读锁,导致GC的写锁请求永远被阻塞,无法执行。
- GC任务持有写锁1秒的逻辑进一步加剧问题:LiteDB的事务被外部锁卡住超过默认1分钟超时阈值,直接抛出异常。
修复方案
- 移除自定义的
AsyncReaderWriterLock:完全信任LiteDB的线程安全实现,所有读写、清理操作直接基于LiteDB自身的API执行。 - 重构GC清理逻辑:
- 放弃长时间持有锁的方式,改为短事务批量删除:比如每次只删除100条过期消息,循环执行直到清理完成,单次事务时长控制在毫秒级,避免阻塞其他操作。
- 调整GC执行间隔,比如从3秒延长至5-10秒,或者根据数据库中过期消息的数量动态调整执行频率。
- 优化LiteDB使用方式:
- 确保整个服务器生命周期内只使用一个
LiteDatabase实例,LiteDB的连接是线程安全的,单实例才能最大化并发效率。 - 若仍存在锁竞争,可适当调大LiteDB的
Timeout配置参数(默认60秒),但这只是临时缓解,核心还是优化清理逻辑。
- 确保整个服务器生命周期内只使用一个
- 优化消息插入效率:
- 高并发场景下,将多个客户端的消息合并为批量插入,减少事务次数,降低LiteDB内部的锁竞争频率。
验证步骤
去掉外部锁后,重新运行100客户端测试:
- 确认GC任务能正常执行清理操作
- 监控消息发送吞吐量是否恢复正常
- 检查是否再出现锁超时异常
内容的提问来源于stack exchange,提问作者Idra
相关产品推荐
相关产品推荐

