问题描述
在写调度模块遇到一个问题。有多个线程负责和各个工作节点通信,在工作节点持续工作的过程中,每完成一个小任务就会返回相应的结果。这时需要将各个线程的结果汇总(这里简单理解为对表示任务进度的变量进行修改),怎么实现呢?
想法1
一个很直观的想法是,各线程都向这个共享变量写入结果。显然,这里面临的就是互斥问题。多个线程同时写,必然会出问题,在此就不说原因了。解决的方法也很简单,加锁。带来的问题也很明显,效率低,当这时候有100个线程要写这个变量时,需要排成队一个一个写。(记得Linux内核中说到过这个问题。等下一定要看看)
想法2
各个线程得到结果后,不去立即修改这个值,而是把自己想要改变的量先保存下来。保存之后通专门负责更新的线程,自己有新的进度需要加到总进度上。更新线程中收到通知后,自己去取这个量。这样一来各个工作线程就不需要阻塞在那里去获取共享变量了。而更新线程再收到通知后,自己再决定什么时候去处理。方法上也更灵活。
目前只想到这个解决方法,有新的想法再更新
想法3
一觉醒来,突然想到第二个想法也会有问题。虽然把工作线程解放了,但更新线程会变得很忙。100个线程不停地notify,更新线程怎么处理的过来。如果有的通知信息没有处理到,便会遗漏信息。要怎么办?工作线程更新数据后设置一个标识,让更新线程周期性的去判断各个标识决定是否去取数据似乎是一个可行的方法。但拿数据的周期为多少是一个问题,实时性无法得到保证。而且这种方法很可能也会出现不一致的问题。例如,更新线程正在读取某工作线程更新的最新数据时,该工作线程此时却又append新的数据。此种情况,要么是正在读取的数据会错误地变成“新数据”,或者新append的数据变成旧数据,这取决于何时去更新标志。这似乎也避免不了要加锁。虽然此种情况最多只有一个读和一个写。但这个加锁和之前有所不同,对工作线程的影响没那么大,因为只有更新线程刚好和工作线程冲突时才会有影响。
总结
面临的问题实际上可描述为多个生产者,一个消费者的模型。而生产者消费者模型的核心问题应该是对缓冲区的使用上。如果生产者和消费者都只有一个,那无疑只使用一个缓冲区即可,生产者放产品的时候只需要关心缓冲区是否已满(这里我们可以看作没有上限?),而消费者在取产品的时候只需要判断是否为空。一个生产者多个消费者,同样只需要一个缓冲区,除非消费者需要“定制产品”,实际上,一个生产者多个消费者的场景更多见,如线程池、socket中的accept等,而一个著名的问题就是“惊群效应”,正因为经典,现在已经有成熟的方法来解决这类问题,核心思想应该是对每个消费者“区别对待”,指名道姓由谁来消费刚生产出来的这个产品。多个生产者一个消费者的情况就灵活多了,既可以所有的生产者把产品放入一个共享的缓冲区中,也可以让每个生产者拥有自己的缓冲区,再或者把生产者分组,一组生产者共用一个缓冲区,这样就有n个缓冲区(1<=n<=生产者数量)。至于哪一种方法更好,则取决于具体情况。一个直观的感觉是,当生产频率越低,则越应该采用共用缓冲区的方式。一个极端的例子是,所有的生产者的生产频率都极低,这时候如果还是每个生产者各自有一个缓冲区,那么消费者很多的工作就会浪费在判断各个缓冲区是否为空上。相反,另一个极端的例子是,如果生产者的生产频率极高,若共用同一个缓冲区,那么在互斥访问缓冲区时,生产者大部分时间会浪费在排队上面。(所以,有方法可以实现不需要互斥访问吗?)
C++之父说过:别去猜想性能如何,证明性能的好坏,需要做的就是测试。原话并非如此,大概是这意思吧。
突然感觉这个问题水很深。瞎想了一通,思路有点混乱,以后要多总结!