我最近在学习tokio,并在写一个练手的项目。遇到一个问题,这里请教下大家。
场景是这样的,这个服务在收到网络请求后,会按惯例生成多个task;并且按照不同的请求内容,还可能继续生成task;总之就是会有很多的task。在task中需要打印日志。不是tracing的那种调试日志,而是自定义格式的业务相关的日志。我的问题就是,怎么让多个task高效的(就是尽量减少竞争)向一个文件里打印日志。
我想到的几个方案如下:
-
用OnceCell定义一个全局的Mutex<File>,然后各个task需要打印日志时,就抢锁并打印。这样的竞争应该非常激烈。
-
创建一个专门写日志文件的task和配套的channel,其他task需要写日志时就把日志写到channel中。那么问题就在于如何找到channel的Sender tx:
2.1. 每次创建一个task,都clone一个tx,并作为参数传给task。但是这样代码很麻烦,每个task都要传tx。后续还可能增加其他类型的日志,就要增加其他tx。而并不是每个task都需要所有tx的。
2.2. 用OnceCell定义一个全局的tx,其他task直接访问这个tx。因为tx在调用send()时并不需要mut,所以我猜这里是没有竞争的(?)。
我觉得上面的2.2方案是最好的。不过看tokio或其他channel的例子里,都是要clone多份tx,并且给每个task传一个tx的,也就是方案2.1。这也就是mpsc里的m的意思吧。所以总感觉方案2.2很山寨。
我的问题是:方案2.2这种OnceCell<Sender>的做法是否有问题?是不是真的没有竞争?是不是最合理最高效的?
评论区
写评论如果不存在卡线程的竞争,大部分情况下同步锁的效率最高,如果流程中可能出现死锁,需要用通道,原则上无脑上crossbeam的同步通道,其他情况下才用tokio相关的异步通道,tokio的一次性通道好像只是语法限定上使代码阅读更清晰,性能上和本身的异步通道似乎没啥差别。
关于通道在多线程以及多协程下的性能对比测试
不需要mut不代表没有竞争
我指的是底层的竞争,刚看了mpsc的源码,sender内部有这么个结构
其中S是一个信号量,就是说不管你上层怎么整,底层是有一个Arc的原子操作,加上信号量来控制多线程同时send时的排队,所以我说该竞争还是竞争。 但是在没有用到多线程的runtime中,异步调度都是在同一线程上进行,那么在每次走到Arc获取锁、信号量acquire时,实际上都不会阻塞,所以我说在这种情况下可能没有竞争。
--
👇
LongRiver: 2.1应该是没有竞争的。(除非说多个线程在同时向channel写消息时可能会有些内部的竞争。这部分应该是高度优化的,可以忽略的)。
我想确认的是2.2有没有竞争?2.2的这种做法是不是很山寨?
另外,你提的两个问题中,第1个是个问题,不过在我的场景下不会去关闭channel所以没有影响。第2个,是用的OnceCell:get()来访问,确实麻烦了些,但会封装到一个函数里,影响不大。
--
👇
asuper: 个人拙见,2.1和2.2没有本质区别,该竞争还是会竞争,不过你的runtime如果不是multithreading的,大概本身不存在竞争问题?不太确定。
另外,选择2.2除了少写几个clone,创建任务的时候看起来干净一点,其实还是有一些缺点
2.1 2.2应该差不多
tx
内部是一个Arc<_>
曾经写过类似的东西来回避原子开销,但实际使用中并没观测到任何提升。
2.1应该是没有竞争的。(除非说多个线程在同时向channel写消息时可能会有些内部的竞争。这部分应该是高度优化的,可以忽略的)。
我想确认的是2.2有没有竞争?2.2的这种做法是不是很山寨?
另外,你提的两个问题中,第1个是个问题,不过在我的场景下不会去关闭channel所以没有影响。第2个,是用的OnceCell:get()来访问,确实麻烦了些,但会封装到一个函数里,影响不大。
--
👇
asuper: 个人拙见,2.1和2.2没有本质区别,该竞争还是会竞争,不过你的runtime如果不是multithreading的,大概本身不存在竞争问题?不太确定。
另外,选择2.2除了少写几个clone,创建任务的时候看起来干净一点,其实还是有一些缺点
个人拙见,2.1和2.2没有本质区别,该竞争还是会竞争,不过你的runtime如果不是multithreading的,大概本身不存在竞争问题?不太确定。
另外,选择2.2除了少写几个clone,创建任务的时候看起来干净一点,其实还是有一些缺点