【问题标题】:How to avoid multi Threading如何避免多线程
【发布时间】:2015-03-05 10:57:01
【问题描述】:

偶遇this question,对this answer印象深刻。

我真的很想听从那个答案的建议,但我无法想象该怎么做。 如何避免多线程?

通常情况下,需要同时处理不同的事情(例如不同的硬件资源或网络),但同时他们需要访问共享数据(如配置、要处理的数据等)。 如何在不使用任何类型的大型状态机或事件循环的情况下单线程解决这个问题?

我知道,这是一个很大的话题,在 Stackoverflow 这样的平台上无法整体回答。我认为我真的应该从上述答案中阅读建议书,但现在我很想在这里阅读一些输入。

也许值得注意的是,我对 C 中的解决方案很感兴趣。Java、C++ 等高级语言,尤其是 Qt 或类似框架等高级语言简化了很多,但纯 C 呢?

非常感谢任何输入。先谢谢大家了

【问题讨论】:

  • 你不应该尝试。这个答案没有给我留下深刻的印象,归结为“如果你实现的多线程真的很糟糕,它的效果会很糟糕”。

标签: c multithreading


【解决方案1】:

您已经提到了事件循环,但我仍然认为它们为许多应用程序提供了一个很好的多线程替代方案,并且在以后添加多线程时也可以作为一个很好的基础,如果有必要的话。

假设您有一个应用程序需要处理用户输入、套接字上接收的数据、计时器事件和信号,例如:

  • 一种多线程设计是产生不同的线程来等待不同的事件源,并让它们在事件到达时同步它们对某个全局状态的操作。这通常会导致混乱的同步和终止逻辑。

  • 单线程设计将具有一个统一的事件循环,该循环接收所有类型的事件并在它们到达时在同一个线程中处理它们。在 *nix 系统上,这可以使用例如完成。 select(2)poll(2)epoll(7)(后者特定于 Linux)。最近的 Linux 版本还提供了 signalfd(2)timerfd (timerfd_create(2)) 和 eventfd(2),以便将其他事件类型干净地安装到此模型中,在其他 unice 上,您可以使用各种技巧,例如pipe(2)s 表示事件。 libevent 是一个很好的库,可以将大部分内容抽象出来,它也适用于其他平台。

除了不必立即处理多线程之外,如果出于性能或其他原因需要,事件循环方法还可以在以后添加多线程:您只需让事件处理程序为某些事件生成线程。将所有事件处理集中在一个位置通常会大大简化应用程序设计。

当您确实需要多个线程(或进程)时,在它们之间建立狭窄且经过良好测试的接口会有所帮助,例如使用同步队列。事件处理程序的另一种设计是让事件生成线程将事件推送到事件队列,然后事件处理程序从中读取并分派它们。这清晰地分离了程序的各个部分。

【讨论】:

  • 事件从何而来?其他线程,大概是:)“事件循环”只是多线程的一种实现,其中通过将输入序列化到事件/消息队列中来保护可变状态数据免受多次访问。这肯定不是一个糟糕的设计(我经常使用它),但它不是多线程的替代品——它是它的一种实现。
  • 它们也可能来自内核,例如当事件与文件描述符相关联时,通常是这种情况。也许您可以争论语义,但是当进程中只有一个线程时,我不认为它是多线程的。 :)
  • @Martin 我不同意你的看法。我认为,事件可能来自任何地方。它们也可能来自事件循环的其他部分。
  • @exilit:阻塞(或“慢”)事件处理程序需要仔细设计,是的。在某些情况下,您可以使用计时器事件。例如,新客户端连接的处理程序可以安排一分钟后设置的“ping 客户端”事件(假设它是非阻塞且“足够快”)设置为一分钟后,该事件处理程序调度另一个“ping 客户端” "事件等。在其他情况下,比如在后台进行长时间运行的计算,更容易产生一个线程并让它在完成时通知事件循环。
  • @exilit:您也可以轻松地将这种方法用于内部生成的事件/消息。您所需要的只是一个消息队列和某种方式向select/poll/epoll()(或只使用libevent)发出信号,表明有新消息可用(完全单线程设计甚至可能不需要)。检测到新消息后,您将处理队列中的第一条消息(或处理队列中当前的所有消息,具体取决于您的设置方式)。尽量避免嵌套消息循环(事件处理程序中的消息循环),因为我的实际经验是它们会变成一团糟。
【解决方案2】:

详细了解continuationscontination-passing style(以及CPS 转换)。

CPS 转换可能是一种“模仿”多线程的系统方法。

您可以查看 CPCContinuation Passing C,作者 Juliusz Chroboczek 和 Gabriel Kerneis),它也是源 C 转换器的源。您还可以阅读 Appel 的旧书:Compiling with Continuations 和 Queinnec 的书 Lisp In Small Pieces

阅读更多关于event loopscallbacksclosurescall stackstail calls的信息。这些概念与您的担忧有关。

另请参阅(几乎已过时的)setcontext(3) Linux 函数和事件循环中的空闲函数,请参阅 this

【讨论】:

    【解决方案3】:

    您可以使用协程来实现并发任务。然后,您必须将控制(cpu)显式传递给另一个协程。它不会在一小段延迟后通过中断自动完成。

    http://en.wikipedia.org/wiki/Coroutine

    【讨论】:

    • 听起来很不错,但我认为在 C 中实现协程可能会很麻烦,不是吗?
    • iirc、bsnes(一个 SNES 仿真器)使用协程在仿真组件(主 CPU、声音 CPU、支持芯片等)之间高效切换。这是一个很好的设计,可以避免在组件之间切换时手动保存和恢复所有状态。因为它需要对时序进行精确控制,所以普通线程也不能很好地工作。 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-10-30
    • 1970-01-01
    • 1970-01-01
    • 2023-03-08
    • 1970-01-01
    • 1970-01-01
    • 2011-03-21
    相关资源
    最近更新 更多