【问题标题】:Processes, threads, green threads, protothreads, fibers, coroutines: what's the difference?进程、线程、绿色线程、protothreads、fibers、协程:有什么区别?
【发布时间】:2011-03-20 11:21:27
【问题描述】:

我正在阅读并发性。对于具有令人困惑的相似定义的术语,我有点不知所措。即:

  • 流程
  • 线程
  • “绿色线程”
  • 原线程
  • 纤维
  • 协程
  • Go 语言中的“Goroutines”

我的印象是,区别在于 (1) 是真正并行还是多路复用; (2) 是否在 CPU、操作系统或程序中进行管理;和 (3..5) 其他一些我无法识别的东西。

对于这些并行处理方法之间的差异,是否有简洁明确的指南?

【问题讨论】:

    标签: multithreading concurrency process parallel-processing fiber


    【解决方案1】:

    好的,我会尽力而为。到处都有警告,但我会尽我最大的努力来说明我对这些术语的理解,并参考与我给出的定义相近的东西。

    • Process:操作系统管理的(可能)真正并发,至少在有合适的硬件支持的情况下。存在于它们自己的地址空间内。
    • Thread:操作系统管理,在与父线程及其所有其他线程相同的地址空间内。可能是真正的并发,多任务是先发制人的。
    • Green Thread:这些是与线程概念相同的用户空间投影,但不是操作系统管理的。可能不是真正的并发,除非可能有多个工作线程或进程同时为它们提供 CPU 时间,因此最好将其视为交错或多路复用。
    • Protothreads:我真的无法从中挑出一个定义。我认为它们是交错的和程序管理的,但不要相信我的话。我的感觉是,它们本质上是同一种“绿色线程”模型的特定于应用程序的实现,并针对应用程序域进行了适当的修改。
    • Fibers:操作系统管理。完全是线程,除了协同多任务处理,因此不是真正的并发。
    • Coroutines:完全是纤程,除了不是操作系统管理的。
    • Goroutines:它们声称与其他任何东西都不一样,但它们似乎完全是绿色线程,例如,在单个地址空间中进行进程管理并多路复用到系统线程上。也许对 Go 有更多了解的人可以浏览营销材料。

    还值得注意的是,在process calculus 意义上,“进程”一词的并发理论还有其他理解。这个定义与上面的定义是正交的,但我只是认为它值得一提,这样如果你在某个地方看到在这个意义上使用的过程就不会产生混淆。

    另外,请注意parallelconcurrent 之间的区别。您可能在问题中使用了前者,而我认为您的意思是后者。

    【讨论】:

    • 非常好的答案,但有两个例外: 光纤和线程并不总是由操作系统管理(尽管它们可以是)。以 N:1 threading 为例(特别是阅读上面关于线程的 Wikipedia 文章)。也就是说,通常线程和光纤应该由操作系统管理,所以上面的答案并不是完全错误的:-)
    • @J Teller,原则上同意,尽管这些概念基本上与列表中的其他概念同构。我主要是想达成共识,但点得很好:)
    • 关于 goroutines,我从 2012 年就发现了这个:programmers.stackexchange.com/questions/222642/…
    • 不错的答案,但为什么协作式多任务处理不是真正并发的?
    • @Mark 协作式多任务处理依赖于在另一件工作发生之前明确的“产量”,因此它们必然是顺序的(例如,在“A”停止执行之前,“B”不会执行) .
    【解决方案2】:

    我基本同意 Gian 的回答,但我对一些并发原语有不同的解释。请注意,这些术语经常被不同的作者不一致地使用。这些是我最喜欢的定义(希望离现代共识不远)。

    • 过程:
      • 操作系统管理
      • 每个都有自己的虚拟地址空间
      • 可以被系统中断(抢占)以允许另一个进程运行
      • 可以与不同处理器上的其他进程并行运行
      • 进程的内存开销很高(包括虚拟内存表、打开的文件句柄等)
      • 进程间创建和上下文切换的时间开销相对较高
    • 线程:
      • 操作系统管理
      • 每个都“包含”在某个特定进程中
      • 同一进程中的所有线程共享相同的虚拟地址空间
      • 可以被系统中断以允许另一个线程运行
      • 可以与不同处理器上的其他线程并行运行
      • 与线程相关的内存和时间开销比进程小,但仍然很重要
        • (例如,上下文切换通常涉及进入内核并调用系统调度程序。)
    • 合作线程:
      • 可能由操作系统管理,也可能不受操作系统管理
      • 每个都“包含”在某个特定进程中
      • 在某些实现中,每个都“包含”在某个特定的操作系统线程中
      • 不能被系统中断以允许合作对等方运行
        • (当然,包含的进程/线程仍然可以被中断)
      • 必须调用特殊的 yield 原语以允许对等协作线程运行
      • 一般不能与合作伙伴并行运行
      • 协作线程主题有很多变体,名称各不相同:
        • 纤维
        • 绿色线程
        • 原线程
        • 用户级线程(用户级线程可以是可中断的/抢占式的,但这是一个相对不寻常的组合)
      • 协作线程的一些实现使用诸如拆分/分段堆栈之类的技术,甚至单独堆分配每个调用帧,以减少与为堆栈预分配大量内存相关的内存开销
      • 根据实现,调用阻塞系统调用(如从网络读取或休眠)将导致整个协作线程组阻塞或隐式导致调用线程屈服
    • 协程:
      • 有些人或多或少将“协程”和“协作线程”同义使用
        • 我不喜欢这种用法
      • 一些协程实现实际上是“浅”的协作线程; yield 只能由“协程入口过程”调用
      • 浅(或半协程)版本比线程更容易实现,因为每个协程不需要完整的堆栈(入口过程只需一帧)
      • 协程框架通常具有 yield 原语,要求调用者明确声明应将协程控制转移到哪个协程
    • 发电机:
      • 受限(浅)协程
      • yield 只能将控制权返回给调用生成器的代码
    • Goroutines:
      • 协作线程和操作系统线程的奇怪混合
      • 不能被中断(如协作线程)
      • 可以在语言运行时管理的 OS 线程池上并行运行
    • 事件处理程序:
      • 事件调度程序为响应发生的某些操作而调用的过程/方法
      • 非常流行的用户界面编程
      • 几乎不需要语言/系统支持;可以在库中实现
      • 一次最多可以运行一个事件处理程序;调度程序必须等待处理程序完成(返回),然后再开始下一个
        • 使同步相对简单;不同的处理程序执行不会在时间上重叠
      • 使用事件处理程序实现复杂任务往往会导致“反向控制流”/“堆栈撕裂”
    • 任务:
      • 经理分配给员工的工作单元
      • worker 可以是线程、进程或机器
        • 当然,任务库使用的工作人员类型会对任务的实现方式产生重大影响
      • 在这个使用不一致和令人困惑的术语列表中,“任务”占据了首位。特别是在嵌入式系统社区中,“任务”有时用于表示“进程”、“线程”或“事件处理程序”(通常称为“中断服务例程”)。它有时也用于一般/非正式地指代任何类型的计算单元。

    我无法阻止自己的一个小毛病:我不喜欢将“真正的并发”一词用于“处理器并行性”。这很常见,但我认为这会导致很多混乱。

    对于大多数应用程序,我认为基于任务的框架最适合并行化。大多数流行的(英特尔的 TBB、苹果的 GCD、微软的 TPL 和 PPL)都使用线程作为工作线程。我希望有一些使用流程的好的替代方案,但我不知道有什么。

    如果您对并发(而不是处理器并行性)感兴趣,事件处理程序是最安全的方法。合作线程是一个有趣的选择,但有点狂野的西部。如果您关心软件的可靠性和稳健性,请不要将线程用于并发。

    【讨论】:

    • “真正的并发”是并发理论中一种特定类型的并发语义的技术术语。 “处理器并行性”可用于实现真正的并发语义。
    • 感谢 Gian 的澄清/更正。我在评论我认为“真正的并发”这个短语的常见非正式用法。抱歉,如果这种用法实际上并不常见,而且更像是我的发烧梦。
    • 我认为人们确实倾向于滥用这个术语,所以我同意人们应该对使用“真正并发”的人表示反对,而他们实际上只是意味着“并行”。不错的答案,顺便说一句,赞成!
    • 从上面没有完全理解协程是什么。
    • 协程是近来相当热门的话题,因此您可以在网络上找到大量信息。简而言之,协程是一种多任务抽象; “调用”协程实际上是分叉该例程的新动态实例。在协程的主体内部,一个特殊的 yield/await 原语可以将控制权转移到另一个正在运行的协程。与协作多线程不同,协程调用的常规例程不能调用yield。异步函数只是协程的不同外衣。
    【解决方案3】:

    Protothreads 只是一个 switch case 实现,它的作用类似于状态机,但使软件的实现变得更加简单。它基于在 case 标签之前保存 a 和 int 值并返回,然后通过读回该变量并使用 switch 来确定从哪里继续来回到 case 之后的点的想法。所以protothread是状态机的顺序实现。

    【讨论】:

      【解决方案4】:

      在实现顺序状态机时,原型线程非常有用。 Protothreads 根本不是真正的线程,而是一种语法抽象,它使编写必须顺序切换状态(从一个到下一个等)的 switch/case 状态机变得更加容易。

      我已经使用protothreads实现了异步io:http://martinschroder.se/asynchronous-io-using-protothreads/

      【讨论】: