进程、线程、绿色线程、protothreads、纤程、协程:它们有什么区别?

68
我正在学习并发技术,但遇到了一些具有混淆相似定义的术语,包括:
  • 进程
  • 线程
  • "绿色线程"
  • Protothreads
  • 纤程
  • 协程
  • Go 语言中的 "Goroutines"
我的印象是它们之间的区别在于:(1) 是否真正并行或多路复用;(2) 在 CPU、操作系统或程序中进行管理;以及 (3..5) 我无法确定的其他一些事情。
是否有简明扼要且明确无误的指南来区分这些并发技术?
4个回答

82

好的,我会尽力而为。虽然有很多需要注意的地方,但我会尽力以我理解的方式解释这些术语和参考资料,以便更加贴近我所提供的定义。

  • 进程: 由操作系统管理,可能是真正的并发,至少在有合适硬件支持的情况下。存在于它们自己的地址空间中。
  • 线程: 由操作系统管理,在与父进程及其所有其他线程相同的地址空间内。可能是真正的并发,并且多任务是抢占式的。
  • 绿色线程: 这些是同样概念的用户空间投影,但不是由操作系统管理。除了可能有多个工作线程或进程同时给它们CPU时间之外,可能不是真正的并发,因此最好将其视为交错或复用。
  • Protothreads: 我无法真正理解这些的定义。我认为它们是交错和程序管理的,但不要相信我的话。我的感觉是,它们实质上是一种特定于应用程序领域的“绿色线程”模型的应用程序特定实现,经过适当的修改。
  • 纤程: 由操作系统管理。与线程完全相同,只是协作式多任务处理,因此不是真正的并发。
  • 协程: 与纤程完全相同,只是不受操作系统管理。
  • Goroutines: 它们声称与其他任何东西都不同,但它们似乎就是绿色线程,即在单个地址空间中由进程管理并复用于系统线程。也许了解Go的人可以理解其市场宣传。
值得注意的是,在并发理论中,“进程”这个术语还有其他理解方式,即“过程演算”意义上的process calculus。这个定义与上述定义无关,但我认为提一下是值得的,以免在某些地方看到“进程”被用于那个意义时产生混淆。
另外,请注意parallelconcurrent之间的区别。你在问题中可能使用了前者,但我认为你想表达的是后者。

1
非常好的答案,只有两个例外:纤程和线程并非始终由操作系统管理(虽然可以)。请看N:1线程(具体请参阅上述Wikipedia文章)作为一个例子。话虽如此,通常情况下,线程和纤程应该由操作系统管理,因此上述答案并不完全错误 :-) - J Teller
不错的回答,但为什么协作式多任务处理并不真正并发? - Mark
@Mark 合作式多任务处理依赖于显式的“yield”操作,以便在执行另一项工作之前进行协调,因此它们必须是顺序的(也就是说,在“A”停止执行之前,“B”不会执行)。 - Gian
如果您将两个yield之间的部分视为“A”部分,则是不会被中断的。但是我认为将“A”视为正在完成的任务更有用,该任务可能会在几个点上产生yield,就像使用操作系统线程一样。在这种情况下,A和B的运行顺序不是固定的或顺序执行的,因此对我来说它似乎是并发的。 - Mark
我对这个哲学观点表示同情,但我倾向于将特定的行为解释为两个进程的顺序交错,而不是真正的并发情况(我在这里所说的“进程”是指过程演算中的概念,而不是操作系统中的概念)。 - Gian
显示剩余2条评论

47

我大部分同意Gian的回答,但对于一些并发原语有不同的解释。请注意,不同的作者常常不一致地使用这些术语。以下是我最喜欢的定义(希望与现代共识不太远)。

  • 进程:
    • 由操作系统管理
    • 每个进程都有自己的虚拟地址空间
    • 可以被系统中断(抢占),以允许另一个进程运行
    • 可以与在不同处理器上运行的其他进程并行运行
    • 进程的内存开销很高(包括虚拟内存表、打开文件句柄等)
    • 创建和在进程之间进行上下文切换的时间开销相对较高
  • 线程:
    • 由操作系统管理
    • 每个线程“包含”在某个特定进程中
    • 同一进程中的所有线程共享同一虚拟地址空间
    • 可以被系统中断以允许另一个线程运行
    • 可以与在不同处理器上运行的其他线程并行运行
    • 与进程相关的内存和时间开销比较小,但仍然相对较大
      • (例如,通常上下文切换涉及进入内核并调用系统调度程序。)
  • 协作线程:
    • 可能是或不是由操作系统管理
    • 每个线程“包含”在某个特定进程中
    • 在某些实现中,每个线程“包含”在某个特定的操作系统线程中
    • 不能被系统中断以允许协作对等方运行
      • (当然,包含进程/线程仍然可以被中断)
    • 必须调用一个特殊的yield原语才能允许对等的协作线程运行
    • 通常不能与对等的协作线程并行运行
    • 有很多变种的协作线程主题,它们有不同的名称:
      • 纤程
      • 绿色线程
      • Protothreads:
        • Protothreads是一种非常轻量级的实现协作式线程的技术(也称为协同式线程)
        • 它与标准线程不同,因为没有操作系统或硬件支持,并且函数在执行时不会被中断
      • 用户级线程(用户级线程可以是可中断/可抢占的,但这是相对不常见的组合)
      • 一些协作式线程的实现使用技术,例如分割堆栈或甚至为每个调用帧单独分配堆内存,以减少与预先为堆栈分配大块内存相关的内存开销
      • 根据实现,调用阻塞系统调用(例如从网络读取或睡眠)将导致整个协作式线程组阻塞或隐式地导致调用线程让出
      • 协程:
        • 有些人将“协程”和“协作式线程”几乎视为同义词
          • 我不喜欢这种用法
        • 一些协程实现实际上是“浅层”协作式线程; yielding只能由“协程入口过程”调用
        • 浅层(或半协程)版本比线程更容易实现,因为每个协程不需要完整的堆栈(仅需要入口过程的一个帧)
        • 通常,协程框架具有yield原语,要求调用者明确指定应将控制权转移给哪个协程控制
      • 生成器:
        • 受限(浅层)协程
        • yield只能将控制权返回给调用生成器的任何代码
      • Goroutines:
        • 协作式和OS线程的奇怪混合体
        • 无法被中断(像协作式线程一样)
        • 可以并行运行在由语言运行时管理的OS线程池上
      • 事件处理程序:
        • 由事件分派程序调用以响应某些动作发生的过程/方法
        • 非常流行的用户界面编程
        • 几乎不需要语言/系统支持;可以在库中实现
        • 最多只能同时运行一个事件处理程序;分派程序必须等待处理程序完成(返回)才能启动下一个
          • 使同步相对简单;不同处理程序执行永远不会重叠
        • 使用事件处理程序实现复杂任务往往会导致“倒置控制流”或“堆栈撕裂”
      • 任务:
        • 由管理器分配给工人池的工作单位
        • 工人可以是线程、进程或机器
          • 当然,任务库使用的工作者类型对于如何实现任务有重大影响
        • 在这个术语使用不一致和混乱的列表中,“任务”这个词夺得桂冠。尤其是在嵌入式系统社区中,“任务”有时被用来表示“进程”、“线程”或“事件处理程序”(通常称为“中断服务例程”),也有时通用/非正式地指代任何计算单元。

      我无法停止自己抱怨的一个小问题是:我不喜欢将“真并发”用作“处理器并行”的短语。虽然它很常见,但我认为它会导致很多混淆。

      对于大多数应用程序,我认为基于任务的框架最适合并行化。大多数流行的框架(如Intel的TBB、Apple的GCD、Microsoft的TPL和PPL)使用线程作为工作者。我希望有一些使用进程的好替代品,但我不知道有哪些。

      如果您对并发感兴趣(而非处理器并行),则事件处理程序是最安全的选择。合作线程是一个有趣的替代方案,但有点像“西部荒野”。如果您关心软件的可靠性和鲁棒性,请不要将线程用于并发。


2
“真正的并发”是并发理论中一种特定的并发语义的技术术语。 “处理器并行性”可以用来实现真正的并发语义。 - Gian
1
感谢你的澄清/更正,Gian。我评论的是我认为“真正并发”这个词组的一般非正式用法。如果这种用法实际上不那么常见,而更像我的幻觉,请接受我的道歉。 - Ben Ylvisaker
1
我认为人们往往滥用这个术语,所以我同意当人们使用“真正的并发”时,应该对此提出异议,因为他们实际上只是指“并行”。顺便说一句,你的回答很好,我点了赞! - Gian
从上面的内容中并没有完全理解协程是什么。 - Rusty Core
协程是当今相当热门的话题,因此您可以在网络上找到大量信息。简而言之,协程是一种多任务抽象;“调用”协程实际上是分叉该例程的新动态实例。在协程的主体内,特殊的yield/await原语可以将控制传递给另一个正在运行的协程。与协作式多线程不同,由协程调用的常规例程不能调用yield。异步函数只是协程的略有不同的外观。 - Ben Ylvisaker

0

Protothreads只是一个开关案例实现,它的作用类似于状态机,但使软件的实现变得更加简单。它基于在case标签之前保存a和int值的想法,并通过读回该变量并使用switch来确定继续执行的位置,从而在case之后返回到原点。因此,protothread是状态机的顺序实现。


0

当实现顺序状态机时,Protothreads非常好用。Protothreads实际上并不是线程,而是一种语法抽象,使得编写一个必须按顺序切换状态(从一个状态到另一个状态等)的switch/case状态机变得更加容易。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接