【问题标题】:forkIO threads and OS threadsforkIO 线程和 OS 线程
【发布时间】:2012-09-12 18:30:14
【问题描述】:

如果我使用 forkIO 创建一个线程,我需要提供一个函数来运行并取回一个标识符 (threadID)。然后我可以通过例如与这种动物交流。工作负载,MVAR 等。但是,据我了解,创建的线程非常有限,只能以 SIMD 方式工作,其中为线程创建提供的功能是指令。我无法更改启动线程时提供的功能。我知道这些用户线程最终是由操作系统映射到操作系统线程的。

我想知道 Haskell 线程和 OS 线程是如何交互的。为什么做完全不同的事情的 Haskell 线程可以映射到同一个 OS 线程?为什么不需要使用固定指令启动 OS 线程(因为它在 forkIO 中是必需的)?调度程序(?)如何识别可能分布的应用程序中的用户线程?换句话说,为什么操作系统线程如此灵活?

最后,有没有办法从应用程序中转储选定线程的堆?

【问题讨论】:

标签: multithreading haskell


【解决方案1】:

首先,让我们解决一个误解:

我了解这些用户线程最终是由操作系统映射到操作系统线程的。

实际上,Haskell 运行时负责从其池中选择特定 OS 线程正在执行的 Haskell 线程。

现在是问题,一次一个。

为什么做完全不同的事情的 Haskell 线程可以映射到同一个 OS 线程?

暂时忽略 FFI,所有 OS 线程实际上都在运行 Haskell 运行时,它跟踪准备好的 Haskell 线程列表。运行时选择一个 Haskell 线程来执行,然后跳转到代码中,一直执行直到线程将控制权交还给运行时。此时,运行时有机会继续执行相同的线程或选择不同的线程。

简而言之:许多 Haskell 线程可以映射到单个 OS 线程,因为实际上 OS 线程只做一件事,即运行 Haskell 运行时。

为什么不需要使用固定指令来启动 OS 线程(因为它在 forkIO 中是需要的)?

我不明白这个问题(我认为它源于第二个误解)。使用固定指令启动 OS 线程与使用固定指令启动 Haskell 线程的意义完全相同:对于每件事,您只需提供一段代码来执行,这就是它的作用。

调度程序(?)如何识别应用程序中可能分布的用户线程?

“分布式”是一个危险的词:通常,它指的是在多台机器上传播代码(大概不是您在这里的意思)。至于 Haskell 运行时如何判断何时有多个线程,嗯,这很简单:你在调用 forkIO 时告诉它。

换句话说,为什么操作系统线程如此灵活?

我不清楚 OS 线程是否比 Haskell 线程更灵活,所以这个问题有点奇怪。

最后,有没有办法从应用程序中转储选定线程的堆?

我实际上根本不知道在多线程应用程序或其他应用程序中转储 Haskell 堆的任何工具。如果您愿意,可以使用vacuum 之类的包转储可从特定对象访问的堆部分的表示。我过去曾使用vacuum-cairo 来可视化这些转储,并取得了巨大的成功。

如需更多信息,您可能会喜欢我的intro to multithreaded gtk2hs programming 的中间两个部分,“Conventions”和“Foreign Imports”,也许还可以享受“The Non-Threaded Runtime”部分的部分内容。

【讨论】:

  • forkOS 更灵活,因为您可以更轻松地与非 Haskell 代码交互。
  • @PhilipJF 好吧,您必须小心所有多线程声明,这也不例外。只有对使用(OS-)线程本地状态的 API 进行多次调用的线程需要从 forkIO 切换到 forkOS;所有其他人都有forkIO 作为一个完全好的选择。
  • 是的。我可能应该说“稍微容易一些”
  • 很好的答案。查看 OS 线程:一个相同的 OS 线程可能同时执行 Haskell 运行时和 Erlang 运行时或 apache 线程?为什么我不能将更多函数映射到用户线程/threadID 帖子创建?
  • @JFritsch 否。Haskell 运行时、Erlang 运行时和 Apache 都假设有自己的进程。您可以在一个线程上运行它们,但只能通过虚拟机。至于第二个问题,可以!只需让与 IO/OS 线程关联的“操作”结束,并检查 MVar (IO ()),告诉它下一步该做什么。
【解决方案2】:

我不会尝试直接回答您的问题,而是尝试为如何实现多线程 Haskell 程序提供一个概念模型。我将忽略许多细节和复杂性。

操作系统使用hardware interrupts 实现preemptive multithreading,以允许多个计算“线程”同时在同一个内核上逻辑运行。

操作系统提供的线程往往很重。它们非常适合某些类型的“多线程”应用程序,并且在 Linux 等系统上,它们基本上是允许多个程序同时运行的相同工具(它们擅长的任务)。

但是,对于 Haskell 等高级语言的许多用途,这些线程有点重。本质上,GHC 运行时就像迷你操作系统一样工作,在操作系统线程之上实现自己的“线程”,就像操作系统在内核之上实现线程一样。

从概念上很容易想象像 Haskell 这样的语言会以这种方式实现。评估 Haskell 包括“强制 thunk”,其中 thunk 是一个计算单元,它可能 1. 依赖于另一个值(thunk)和/或 2. 创建新的 thunk。

因此,可以想象多个线程同时评估 thunk。一个人会构建一个待评估的 thunk 队列。每个线程将弹出队列的顶部,并评估该 thunk 直到它完成,然后从队列中选择一个新的 thunk。 par 及其同类操作可以通过向该队列添加一个 thunk 来“激发”新的计算。

将此模型扩展到 IO 操作也不是特别难以想象。不是每个简单地强制纯 thunk,我们想象 Haskell 计算的单元会更复杂一些。此类运行时的伪 Haskell:

type Spark = (ThreadId,Action)
data Action = Compute Thunk | Perform IOAction

注意:这只是为了概念理解,不要认为事情是这样实现的

当我们运行 Spark 时,我们会寻找“抛出”到该线程 ID 的异常。假设我们没有,执行包括强制 thunk 或执行 IO 操作。

很明显,我在这里的解释非常随意,忽略了一些复杂性。此外,GHC 团队还撰写了 Marlow 等人的“Runtime Support for Multicore Haskell”等优秀文章。您可能还想查看有关操作系统的教科书,因为它们经常深入探讨如何构建调度程序。

【讨论】:

    猜你喜欢
    • 2010-12-25
    • 1970-01-01
    • 2011-04-13
    • 1970-01-01
    • 2015-11-09
    • 2015-02-23
    • 2010-09-26
    相关资源
    最近更新 更多