6

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

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

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

4

2 回答 2

12

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

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

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

现在问题,一次一个。

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

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

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

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

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

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

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

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

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

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

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

有关更多信息,您可能会喜欢中间的两个部分,“约定”和“外国进口”,从我的多线程 gtk2hs 编程简介,以及“非线程运行时”部分的部分内容。

于 2012-09-12T18:51:29.000 回答
9

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

操作系统使用硬件中断实现抢占式多线程,以允许多个计算“线程”同时在同一内核上逻辑运行。

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

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

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

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

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

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

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

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

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

于 2012-09-12T19:34:11.697 回答