2012-09-12 260 views
4

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

我想知道Haskell线程和OS线程如何进行接口。为什么Haskell线程可以将完全不同的东西映射到同一个OS线程?为什么不需要用固定指令启动OS线程(因为它在forkIO中需要)?调度程序(?)如何识别可能分发的应用程序中的用户线程?换句话说,为什么操作系统线程如此灵活?

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

+2

您可能有兴趣阅读http://community.haskell.org/~simonmar/papers/conc-ffi.pdf,它很好地解释了多线程Haskell的实现。这有点旧,但仍然非常重要。尽管有这个头衔,但它远远超过FFI。 –

回答

10

首先,让我们来讨论一个快速的误解:

据我所知,这些用户线程都最终被映射到操作系统线程操作系统。

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

现在的问题,一次一个。

为什么Haskell线程可以将完全不同的东西映射到同一个OS线程?

当前忽略FFI,所有操作系统线程实际上都在运行Haskell运行时,该运行时会跟踪已准备好的Haskell线程列表。运行时选择一个Haskell线程来执行,并跳转到代码中,直到线程返回运行时为止。那时,运行时有机会继续执行相同的线程或选择另一个线程。

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

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

我不明白这个问题(我想这是源于第二个误解)。你用一个固定的指令来启动OS线程,就像你用一条固定的指令启动Haskell线程一样:对于每一件事,你只需给出一块代码来执行,这就是它的作用。

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

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

换句话说,为什么OS线程如此灵活?

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

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

我实际上并不知道任何在多线程应用程序或其他应用程序中转储Haskell堆的工具。如果喜欢,可以使用类似vacuum这样的软件包转储可从特定对象访问的堆部分表示。我已经使用vacuum-cairo在过去取得巨大成功的可视化这些转储。

有关更多信息,您可以从我的intro to multithreaded gtk2hs programming中享受中间两部分,“惯例”和“外国进口”,或许也可以参阅“无螺纹运行时”部分的部分内容。

+0

'forkOS'更灵活,您可以更轻松地与非haskell代码进行交互。 –

+1

@PhilipJF那么,你必须小心所有的多线程声明,这也不例外。只有使用(OS-)线程本地状态的API进行多次调用的线程需要从'forkIO'切换到'forkOS';所有其他人都有'forkIO'作为一个完全不错的选择。 –

+0

是的。我可能应该说“稍微容易” –

8

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

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

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

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

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

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

将此模型扩展到IO操作并不是特别难以想象。我们认为Haskell计算的单位稍微复杂一些,而不是简单地强制纯粹的thunk。伪哈斯克尔这种运行时:

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

注意:这仅仅是为了概念上的理解,不要以为事情以这种方式实现

当我们运行一个火花,我们期待为例外“扔”到该线程ID。假设我们没有,执行包括强制执行thunk或执行IO操作。

显然,我在这里的解释一直很手动,并且忽略了一些复杂性。有关更多信息,GHC团队撰写了优秀的文章,例如Marlow等人的“多核Haskell运行时支持”。您可能还想看看操作系统上的教科书,因为他们经常深入了解如何构建调度程序。