用户级线程和内核支持线程之间的区别是什么?

93

我一直在查阅关于这个主题的一些笔记,虽然我对线程有一般的了解,但是我对用户级线程和内核级线程之间的差异不太确定。

我知道进程基本上是由多个线程或单个线程组成,但是这些线程是前面提到的两种类型中的哪一种?

据我所知,内核支持的线程可以访问内核以进行系统调用和其他用户级线程无法使用的操作。

那么,用户级线程只是由程序员创建的线程,然后利用内核支持的线程执行不能正常执行的操作吗?


2
在x86 Windows上的进程中,线程在用户模式和内核模式(程序和操作系统/系统调用之间)之间交替。存在内核工作线程,但它们并不直接参与进程的执行。这是一个相当常见的安排。 - Alexey Frunze
5个回答

124

操作系统级线程 vs 绿色线程

为了更加清晰,我通常会说“操作系统级线程”或“本地线程”,而不是“内核级线程”(在我的原始回答中与“内核线程”混淆了)。操作系统级线程由操作系统创建和管理。大多数编程语言都支持它们(如C、最近的Java等)。但是,由于您需要100%负责防止问题,所以它们非常难以使用。在某些语言中,即使本地数据结构(例如哈希表或字典)也需要额外的锁定代码才能正常工作。

与操作系统级线程相反的是由您的编程语言管理的绿色线程。这些线程根据语言的不同具有各种名称(如C中的协程,Go中的goroutine,Ruby中的纤程等)。这些线程仅存在于您的语言中,而不是在您的操作系统中。因为语言选择上下文切换(即在语句结束时),所以它可以防止许多微妙的竞争条件(例如查看部分复制的结构或需要锁定大多数数据结构)。程序员看到“阻塞”调用(即data = file.read()),但语言将其转换为对操作系统的异步调用。然后,语言允许其他绿色线程在等待结果时运行。

对程序员来说,绿色线程要简单得多,但它们的性能有所不同:如果您有很多线程,绿色线程可以更好地处理CPU和RAM。另一方面,大多数绿色线程语言无法利用多个核心。(您甚至无法再购买单核计算机或手机了!)。而且,一个糟糕的库可以通过执行阻塞OS调用来停止整个语言。

最好的方法是每个CPU拥有一个OS线程,并且有许多绿色线程可以神奇地移动到OS线程上。像Go和Erlang这样的编程语言可以实现这一点。

系统调用和其他用户级线程无法使用的功能

这只是一半正确。是的,如果您自己调用操作系统(即执行阻塞操作),则可能会轻松引起问题。但是,语言通常具有替代方法,因此您甚至不会注意到。这些替代方法确实调用内核,但与您想象的略有不同。


内核线程 vs 用户线程

编辑:这是我的原始答案,但它是关于用户空间线程与仅内核线程(事后看来)可能不是问题。

用户线程和内核线程完全相同。 (您可以通过查看/ proc /并查看内核线程是否存在来了解情况。)

用户线程是执行用户空间代码的线程。但是,它随时可以调用内核空间。即使在提高的安全级别下执行内核代码,它仍然被视为“用户”线程。

一个Kernel线程只运行内核代码,不与用户空间进程关联。它们类似于“UNIX守护程序”,但是它们是仅限内核的守护程序。因此,可以说内核是一个多线程程序。例如,有一个用于交换的内核线程。这迫使所有交换问题“串行化”为单个流。
如果用户线程需要某些东西,它将调用内核,将该线程标记为睡眠状态。稍后,交换线程找到数据,因此将用户线程标记为可运行。更晚一点,“用户线程”从内核返回到用户空间,就好像什么也没有发生过。
事实上,所有线程最初都在内核空间中,因为clone()操作发生在内核空间中。(并且在返回到用户空间的新进程之前需要进行大量的内核账户处理。)

谢谢回复!“操作系统级线程是由操作系统创建和管理的。大多数语言都支持它们。(C、最近的Java等) ”您能指出C和Java提供操作系统级线程的哪个库、结构或类吗? - user3284469
1
这是否意味着如果“内核级线程”正在执行用户空间代码,它也是一个“用户线程”? - Viraj
内核线程不执行任何用户空间代码,因为它们没有与任何用户进程相关联。当用户线程进行系统调用时,它会进入内核级别。因此,这取决于您的意思是“内核线程”(否)还是“调用内核的线程”(是)。 - BraveNewCurrency
与用户级线程/内核级线程/轻量级进程/内核工作线程相关的术语可能是最令人困惑的事情。这些术语似乎被过度使用到难以理解它们之间的区别。你做得很好地解释了。当我指的是由内核创建并由内核执行工作的线程时,我尝试使用内核工作线程。 - in70x

32

在我们开始比较之前,让我们先了解一下什么是线程。线程是独立进程域内的轻量级进程。它们是必需的,因为进程很重,消耗大量资源,更重要的是,

两个独立的进程无法共享内存空间。

假设你打开一个文本编辑器。它是在内存中执行的独立进程,具有单独的可寻址位置。您需要此进程中的许多资源,如插入图形、拼写检查等。为每个这些功能创建单独的进程并在内存中独立维护它们是不可行的。为避免这种情况,可以在单个进程内创建多个线程,这些线程可以共享公共内存空间,在进程内独立存在。

现在,回到你的问题,逐个来看。

我不太确定用户级线程和内核级线程之间的区别。

根据其执行域,线程大致分为用户级线程和内核级线程。也有一些情况下,一个或多个用户级线程映射到一个或多个内核级线程。

- 用户级线程

用户级线程主要在应用程序级别,其中应用程序创建这些线程以维持其在主内存中的执行。除非需要,否则这些线程与内核线程隔离工作。

这些线程更容易创建,因为它们不必引用许多寄存器,并且上下文切换比内核级线程快得多。

用户级线程主要可以在应用程序级别引起变化,而内核级线程继续以自己的速度执行。

- 内核级线程

这些线程大多独立于正在进行的进程,并由操作系统执行。

操作系统需要这些线程来执行诸如内存管理、进程管理等任务。

由于这些线程维护、执行和报告操作系统所需的进程,因此创建和管理这些线程更加昂贵,这些线程的上下文切换也很慢。

大多数内核级线程无法被用户级线程抢占。

MS DOS written for Intel 8088 didn't have dual mode of operation. Thus, a user level process had the ability to corrupt the entire operating system.

- 用户级线程映射到内核级线程

这可能是最有趣的部分。许多用户级线程映射到内核级线程,这些线程与内核进行通信。

一些重要的映射包括:

一对一

当一个用户级线程映射到一个内核级线程时。

优点:每个用户线程映射到一个内核线程。即使其中一个用户线程发出阻塞系统调用,其他进程也不会受到影响。

缺点:每个用户线程都需要一个内核线程进行交互,而内核线程创建和管理成本很高。

多对一

当多个用户线程映射到一个内核线程时。

优点:不需要多个内核线程,因为相似的用户线程可以映射到一个内核线程。

缺点:即使其中一个用户线程发出阻塞系统调用,映射到该内核线程的所有其他用户线程都会被阻塞。

此外,无法实现良好的并发性,因为内核一次只能处理一个内核线程。

多对多

当许多用户线程映射到相等或较少数量的内核线程时。程序员决定有多少个用户线程将映射到多少个内核线程。一些用户线程可能只映射到一个内核线程。

优点:实现了极高的并发性。程序员可以决定一些可能会发出阻塞系统调用的危险线程,并将它们与一对一映射放在一起。

缺点:如果没有谨慎决定内核线程的数量,系统可能会变慢。

您问题的另一部分:

内核支持的线程可以访问内核以进行系统调用和其他用户级线程不可用的操作。

因此,用户级线程是否只是由程序员创建的线程,然后利用内核支持的线程执行由于其状态而无法正常执行的操作?

部分正确。几乎所有的内核线程都可以访问系统调用和其他关键中断,因为内核线程负责执行操作系统的进程。用户线程将无法访问其中一些关键功能。例如,文本编辑器永远不能启动一个具有更改进程物理地址能力的线程。但是,如果需要,用户线程可以映射到内核线程并发出一些它作为独立实体无法执行的系统调用。内核线程将然后将此系统调用映射到内核并执行必要的操作。


4
“将用户级线程映射到内核级线程”是什么意思? - ForeverWintr
@Yusuf Hassan "优点:每个用户线程映射到一个内核线程。即使其中一个用户线程发出阻塞系统调用,其他进程线程也不会受到影响。" 也许有一个打字错误。我认为你的意思是“其他进程线程不会受到影响。” - frhack

4

摘自此处:

内核级线程

为了使并发更便宜,将进程的执行方面分离成线程。因此,操作系统现在管理线程和进程。所有线程操作都在内核中实现,操作系统调度系统中的所有线程。操作系统管理的线程称为内核级线程或轻量级进程。 NT: 线程 Solaris: 轻量级进程(LWP)。

在这种方法中,内核知道并管理线程。在这种情况下不需要运行时系统。内核不是在每个进程中使用线程表,而是有一个线程表来跟踪系统中的所有线程。此外,内核还维护传统的进程表来跟踪进程。操作系统内核提供系统调用来创建和管理线程。

优点:

由于内核完全知道所有线程,调度程序可能决定给具有大量线程的进程比给具有少量线程的进程更多的时间。 内核级线程特别适合频繁阻塞的应用程序。

缺点:

内核级线程速度慢且效率低下。例如,线程操作的速度比用户级线程慢几百倍。 由于内核必须管理和调度线程以及进程,因此需要一个完整的线程控制块(TCB)来维护有关线程的信息。因此会有重大开销和内核复杂性的增加。

用户级线程

内核级线程使并发相对于进程变得更便宜,因为需要分配和初始化的状态要少得多。但是,对于细粒度的并发,内核级线程仍然存在过多的开销。线程操作仍需要系统调用。理想情况下,我们需要将线程操作速度与过程调用一样快。内核级线程必须是通用的,以支持所有程序员、语言、运行时等的需求。对于这种细粒度的并发,我们仍然需要更“便宜”的线程。 为了使线程便宜又快,需要在用户级别实现。用户级线程完全由运行时系统(用户级库)管理。内核不知道用户级线程,并将其像单线程进程一样管理。用户级线程很小且快速,每个线程由PC、寄存器、堆栈和小型线程控制块表示。创建新线程、在线程之间切换和同步线程通过过程调用完成。即没有内核参与。用户级线程比内核级线程快百倍。

优点:

这种技术最明显的优点是可以在不支持线程的操作系统上实现用户级线程包。用户级线程不需要修改操作系统。简单表示:每个线程只需由一个PC、寄存器、堆栈和一个小的控制块组成,所有这些都存储在用户进程地址空间中。简单管理:这意味着创建线程、在线程之间切换以及线程之间的同步都可以在没有内核干预的情况下完成。快速高效:线程切换的开销并不比过程调用更大。
缺点: 用户级线程并不是完美的解决方案,就像其他所有东西一样,它们也是一种权衡。由于用户级线程对操作系统不可见,因此它们与操作系统集成不良。结果,操作系统可能会做出糟糕的决策,如调度具有空闲线程的进程、阻塞启动I/O的线程的进程,尽管该进程还有其他可运行的线程,并且取消安排拥有锁的线程的进程。解决这个问题需要内核和用户级线程管理器之间的通信。 线程之间缺乏协调,与操作系统内核之间也缺乏协调。因此,整个进程无论有一个线程还是1000个线程,在分配时间片时都被视为一个整体。每个线程都需要放弃控制权,以便其他线程可以运行。 用户级线程需要非阻塞系统调用,即多线程内核。否则,即使在进程中还有可运行的线程,整个进程也会在内核中被阻塞。例如,如果一个线程导致缺页错误,该进程将被阻塞。

3
一些开发环境或者编程语言会增加像线程一样的功能,这些功能是为了利用环境的某些特性而编写的,例如图形用户界面(GUI)环境可以实现一些线程功能,它可以在每个事件循环中切换用户线程。
游戏库可能会为角色添加一些类似线程的行为。有时,用户线程的行为可以以不同的方式来实现,例如我经常使用 Cocoa,它具有定时器机制,可以每 x 秒执行您的代码,使用分数秒,就像一个线程。Ruby 有一个 yield 特性,类似于协作式线程。用户线程的好处是它们可以在更可预测的时间上进行切换。每次线程重新启动时,内核线程需要加载它正在处理的任何数据,这可能需要时间。而用户线程可以在完成某些数据处理后进行切换,因此无需重新加载数据。
我还没有遇到过和内核线程看起来相同的用户线程,只有类似于定时器的线程机制,虽然我在早期的教科书中读到过有关它们的内容,所以我想知道它们是否曾经在过去更受欢迎,但随着真正多线程操作系统(现代 Windows 和 Mac OS X)和更强大的硬件的出现,我想知道它们是否已经失去了优势。

3

用户级线程

  1. 该库提供了创建、调度和管理线程的功能,没有内核的支持。
  2. 内核不知道用户级线程的创建和调度是在用户空间中完成的,没有内核干预。
  3. 用户级线程通常很快地创建和管理,但也有缺点。
  4. 如果内核是单线程的,则任何执行阻塞系统调用的用户级线程都会导致整个进程阻塞,即使应用程序中有其他线程可运行。
  5. 用户线程库包括 POSIX Pthreads、Mach C-threads 和 Solaris 2 UI-threads。

内核级线程

  1. 内核在内核空间中执行线程的创建、调度和管理。
  2. 内核级线程通常比用户级线程慢。
  3. 如果线程执行阻塞系统调用,则内核来管理线程。
  4. 在多处理器环境下,内核可以将线程调度到不同的处理器上。
  5. 包括 Windows NT、Windows 2000、Solaris 2、BeOS 和 Tru64 UNIX(以前是 Digital UN1X)在内的操作系统支持内核级线程。

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