0

我试图让我的头脑围绕 C++ 中的多线程,想出一个适合我的通用实现。每个人都有不同的实现,Awesome CPP列出了 39 个库。在我看来,这是一个与任何领域的任何后勤调度问题相同的后勤问题。

在我看来,有两种明显的方法可以重复执行这项工作abc

  1. 分成abc3 个单独的任务:a, b& c。产生 x 个线程。排队。进来的作业被添加到队列中。每个线程从队列中抓取下一个任务,并在任务结束时将其放回队列中等待下一个任务。他们可以直接访问队列,或者他们都可以与为他们提供任务的中央“管理器”或“调度器”线程进行通信。

  2. abc独立地在 x 个单独的线程上按顺序执行(并行性。)

(1) 存在一个问题,即在保持队列和处理其上的竞争条件时可能存在大量开销。(1)在其他方面是直观的,对我来说很有意义。这就是我在现实生活中遇到现实生活中的问题时会做的事情。从字面上看,这就是公司在现实世界中的运作方式。

(2)存在任何阻塞导致整个线程阻塞,使CPU线程空闲的问题。并且(2)在较少的用例中的灵活性和适用性要差得多。从好的方面来说,任务之间没有开销。

问题1:(1)不也有同样的阻塞问题吗?如果一个线程从文件中读取,它必须等待磁盘。这通常是如何解决的,是否有某种方法可以在它执行诸如从磁盘读取或写入之类的操作时暂时返回,或者这通常只是通过运行的线程多于 CPU 线程并希望一次不会有太多块来解决?

在我看来,(1)显然是更好的解决方案,除了它将任务限制为中型到大型任务。用它来做一些简单的数学并行化(只是一个例子)是没有意义的,因为处理队列需要比实际处理任务更长的时间。因此,任何给定任务的 (1) 值与存储机制(队列)的开销和任务大小之间的差异成反比。从表面上看,这听起来不错,直到您意识到拆分为任务的效率本身与任务的大小成正比。简而言之:理论上您希望每个任务都较小以提高整体效率,但实际上您希望每个任务都较大以最小化队列的开销。

很明显,需要一些存储机制因为如果没有记录机制,您将无法跟踪某件事,它不一定是严格的队列,而是在等待被拾取时将任务记录在内存中的任何形式。队列的优化(我用的是松散的词,不是严格意义上的队列类型)这里的#1重要因素。任务接收其有效载荷的成本越低越好。

这让我想到了问题 2:这就是 C++20 协程有用的地方吗?我花了几个小时阅读协程教程,但仍然不清楚它们有什么用处。我想我明白他们的所作所为。如果我做对了,他们允许一种特殊类型的函数(协程)在中间暂停,将其处理连同有效负载一起返回给调用者,调用者稍后可以恢复它。但我为什么要这样做?我不能通过将函数分成两部分来做到这一点吗?

问题 3:协程是否意味着任务调度程序线程使用协程以某种方式优化队列?还是只是为了让您线性编写代码,然后将这些产量放入其中以将其分解?在这种情况下,如果我已经按照设计将我的工作分成单独的任务,它对我没有用处?

问题4:我想在这里重新发明轮子吗?这个问题已经解决了吗?如果是这样,为什么会有这么多不同的实现?

4

1 回答 1

0

Q1:不,它更可能有不同的阻塞问题。

Q2:协程有很多应用;尝试在“这就是 X 的用途吗?”中替换 X。X = { 同时,如果,返回,指针,... }。不要向标准机构(尤其是那个)寻求洞察力;他们最擅长标点符号和拼写检查。

Q3:协程可以用来优化各种结构,但使用这种形式的真正目的是让你的程序尽可能自然地表达问题。Go 的 Go-routines 是如何智能使用协程的一个更好的例子。

Q4:可能;几乎可以肯定;因为许多解决方案是不充分的。

Q1+Q4。没有单一的阻塞问题,想到的一些是:死锁、活锁、不必要的顺序、不可扩展、慢。一些结构 {{ 线程、协程、线程 + 协程 } * { 锁、条件、消息传递 }} 有助于解决其中一些问题,但会引发其他问题。我最喜欢的是 { (threads + coroutines) * (message passing) },它通常对所有东西都有好处,除了慢。

于 2021-03-22T14:31:58.007 回答