假设存在多线程API,它是从输入队列读取请求,处理请求然后在输出队列上写入数据。
但是存在一个约束:所有传入请求的结果应按照收到的顺序写入O / P队列。
示例:
要求:[r1,r2,r3]
输出应该是相同的顺序:[o1,o2,o3]
问题:如何同步线程,使输出队列中的所有数据保持顺序,如输入队列??
我给了面试官3个答案:
第一回答:
假设线程[t1,t2,t3]处理传入的请求[r1,r2,r3]
我们可以加入那种情况: t2加入t1 t3加入t2 这样就可以维持处理顺序。
以上解决方案的问题: 1.必须为每个请求创建新线程。 2.处理并写入输出后,线程必须退出。
显然上面的解决方案是不可行的,因为如果有一个线程池并且我们有有限数量的线程会怎样。
第二解决方案:
当输入队列中的线程发出请求时,锁定输出队列
线程t1在输出队列上取r1和mutex锁
显然t2和t3将无法处理请求r2和r3。
上述解决方案的问题:
1.在这方面不能实现并发
2.单个线程只能处理单个请求
3.此处不能应用线程池概念。
第三解决方案:
我们可以在全局数据结构中维护请求的顺序。
线程t1取r1并在数据结构中添加优先级p1 数据结构:[p1]
线程t2取r1并在优先级数据结构中添加p2
数据结构:[p1,p2]
现在假设线程t2完成了对r2的处理并且愿意在O / P队列中写入输出o2。 t2将检查数据结构,并将发现p1中的第一个元素与优先级数据结构中输入的t2不同。所以t2将继续进行有限的等待时间并再次检查。
同时t1现在完成并愿意将输出o1写入O / P队列。它检查数据结构并发现第一个元素是由t1本身插入的p1。所以t1将写入输出队列,并将从全局优先级数据结构中删除p1。
请注意:
[全局优先级数据结构可以使用互斥锁和其他机制使线程安全。这里的问题不同:)]
我已经向面试官提供了上述答案,我认为我的第3个答案是正确的。
但面试官似乎并不满意我的答案。
任何人都可以为这个问题提供正确的解决方案吗?
根据给出的答案,我想补充一些细节:
1)主线程不负责将数据写入输出队列。每个线程将写入结果为的O / P队列
2)单个线程可能以连续的方式处理多个请求
例如:
线程T1进程请求R1然后线程T2进程请求R2然后线程T1将输出o1放入O / P队列并从I / P队列中选择另一个请求R3进行处理。
所以现在线程T1处理R3和线程T2正在处理R2请求。
3)也可能有许多客户端向I / P队列写入请求。它不是以特定顺序和优先级提出请求的客户 优先级基于先到先服务器设置。
客户端C1在另一个客户端C2发出R2请求之前放置R1请求。 因此,R1请求的输出应首先写入O / P队列。
答案 0 :(得分:0)
你的第3个答案已经结束了。
根据您的说法,您总是按顺序接收输入,因此很容易为添加到队列中的每个对象分配优先级。
任何线程按顺序拾取元素,但是对每个元素的处理可能比下一个元素花费的时间更长,因此输出队列中的结果不会按顺序给出。优先级用于将它们按照收到的顺序排序。
读取输出的进程以第一优先级开始,并等待具有该特定优先级编号的输出在队列中。一旦它被用完,下次使用,我们首先检查下一个优先级是否已经可用并使用它或它不存在我们阻止直到新数据出现在输出队列中。
如Eljay所述,std::map<unsigned long, your-object-ptr>
可用于unsigned long
为优先级的输出队列。
只有小故障,unsigned long
在某些时候会恢复为0.你的算法应确保它可以处理特殊情况(如果它可能发生)(即你的软件运行)很长一段时间或很快你总是在队列中得到大量的消息。
要证明3个元素的要点,先说第3个,然后是第1个,最后是第2个,有步骤
Input Queue
[p1, o1]
[p2, o2]
[p3, o3]
3 Threads pick up the objects and start working
Output Queue
-- first incoming result
[p3, o3] -- main thread still wait for p1
-- second incoming result
[p1, o1] -- eat this one!
[p3, o3]
-- check for next result, not present, wait
[p3, o3]
-- third incoming result
[p2, o2] -- eat this one!
[p3, o3]
-- check for next result, present! return immediately
[p3, o3] -- eat that one, finally
队列中的排序不是必需的。如果你只期望2或3个元素,不对它们进行排序不会产生巨大的差异。如果您可以获得更多,排序将成为一个很好的优化,因为它将以这种方式更快地读取数据。
根据附加信息,约束是以正确的顺序添加到输出队列。不在输出队列中获取正确的顺序。在这种情况下,我们执行类似于上面的操作,只是我们阻止添加到队列,除非允许将下一个项目添加到所述队列。
因此,如果队列尚未收到项o1
(输出1),则尝试添加o2
或o3
会阻止当前线程,直到添加o1
为止。
从我上面的例子中我会得到:
Output Queue
-- first incoming result is `o3`
-- thread that handled `o3` blocks until `o2` was added to the queue
-- second incoming result is `o1`
[o1] -- add this one! counter is now on `o2`
-- wake up threads waiting on queue, thread with `o3` goes back to sleep
-- third incoming result is `o2`
[o2] -- add this one! counter is now on `o3`
-- wake up threads waiting on queue, thread with `o3` is allowed now!
[o3]
无论哪种方式都有效。我个人更喜欢另一个,因为它允许线程立即处理下一个数据包,因为它们的负载已保存在队列中。但是,如果您的资源有限并且需要确保无论如何完成工作(在负载较重的系统上,它可能是唯一的好解决方案),这种方式会更好。
当然,&#34;唤醒线程等待队列&#34;可以优化为仅唤醒线程等待,因为o2
或o3
现在可以专门添加到队列中。这取决于您的实现如何运作。最简单的是将它们全部唤醒并让每个线程自行决定是否有下一个要保存的对象。
请注意,在这种情况下,我们不需要优先级,只需要一个计数器来知道接下来需要保存哪个回复。
一种可能的缺点,无论哪种方式:如果一个线程决定不返回任何输出,你的算法会阻塞(无论哪种方式。)