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

Posted

技术标签:

【中文标题】用户级线程和内核支持的线程之间的区别?【英文标题】:Difference between user-level and kernel-supported threads? 【发布时间】:2013-04-05 17:15:55 【问题描述】:

我一直在查看基于这个主题的一些笔记,虽然我对线程有一般的了解,但我并不确定用户级线程和内核级线程之间的区别 .

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

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

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

【问题讨论】:

在 x86 Windows 上的进程中,线程在用户模式和内核模式之间交替(在程序和操作系统/系统调用之间)。存在内核工作线程,但它们不直接参与也不总是参与进程的执行。这是一种相当普遍的安排。 【参考方案1】:

一些开发环境或语言会添加自己的线程之类的功能,这是为了利用环境的一些知识而编写的,例如,GUI 环境可以实现一些线程功能,在每个事件循环上在用户线程之间切换。

游戏库可能有一些类似线程的角色行为。有时,类似用户线程的行为可以以不同的方式实现,例如我经常使用可可,它有一个计时器机制,每 x 秒执行一次你的代码,使用几分之一秒,它就像一个线程。 Ruby 有一个类似于协作线程的 yield 特性。用户线程的优点是它们可以在更可预测的时间切换。对于内核线程,每次线程再次启动时,它都需要加载它正在处理的任何数据,这可能需要时间,对于用户线程,您可以在完成处理某些数据时切换,因此不需要重新加载。

我没有遇到过看起来与内核线程相同的用户线程,只有像计时器这样的类似线程的机制,尽管我在较旧的教科书中读过它们,所以我想知道它们是否在过去,但随着真正的多线程操作系统(现代 Windows 和 Mac OS X)和更强大的硬件的兴起,我想知道它们是否已经失宠了。

【讨论】:

【参考方案2】:

用户线程

    该库提供对线程创建、调度和管理的支持,而内核不提供支持。 内核不知道用户级线程的创建和调度是在用户空间中完成的,无需内核干预。 用户级线程通常可以快速创建和管理,但它们也有缺点。 如果内核是单线程的,那么任何执行阻塞系统调用的用户级线程都会导致整个进程阻塞,即使其他线程可以在应用程序中运行。 用户线程库包括 POSIX Pthreads、Mach C-threads、 和 Solaris 2 UI 线程。

内核线程

    内核在内核空间中执行线程创建、调度和管理。 内核线程的创建和管理速度通常比用户线程慢。 如果线程执行阻塞系统调用,则内核正在管理线程。 多处理器环境,内核可以在不同的处理器上调度线程。 5.包括Windows NT、Windows 2000、Solaris 2、BeOS和Tru64 UNIX(原Digital UN1X)——支持内核线程。

【讨论】:

【参考方案3】:

编辑:这个问题有点令人困惑,所以我以两种不同的方式回答。

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

为了清楚起见,我通常说“操作系统级线程”或“本机线程”而不是“内核级线程”(我在下面的原始答案中与“内核线程”混淆了。)创建操作系统级线程并由操作系统管理。大多数语言都支持它们。 (C、最近的 Java 等)它们非常难以使用,因为您 100% 负责防止出现问题。在某些语言中,即使是本机数据结构(例如哈希或字典)也会在没有额外锁定代码的情况下破坏。

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

对于程序员来说,绿色线程要简单得多,但它们的性能会有所不同:如果您有很多线程,那么绿色线程对于 CPU 和 RAM 都可能更好。另一方面,大多数绿色线程语言不能利用多核。 (您甚至不能再购买单核计算机或手机了!)。一个糟糕的库可以通过阻塞操作系统调用来停止整个语言。

两全其美是每个 CPU 拥有一个 OS 线程,以及许多神奇地移动到 OS 线程上的绿色线程。 Go 和 Erlang 等语言可以做到这一点。

系统调用和其他用户级线程不可用的用途

这只是对了一半。是的,如果你自己调用操作系统(即做一些阻塞的事情),你很容易引起问题。但是这种语言通常有替换,所以你甚至不会注意到。这些替换确实调用了内核,只是与您想象的略有不同。


内核线程与用户线程

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

用户线程和内核线程完全相同。 (您可以通过查看 /proc/ 看到内核线程也在那里。)

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

内核线程是只运行内核代码且与用户空间进程无关的线程。这些类似于“UNIX 守护程序”,只是它们是仅内核的守护程序。所以你可以说内核是一个多线程程序。例如,有一个用于交换的内核线程。这会强制所有交换问题“序列化”到单个流中。

如果用户线程需要某些东西,它会调用内核,从而将该线程标记为休眠。稍后,交换线程找到数据,因此将用户线程标记为可运行。再后来,“用户线程”从内核返回到用户态,就好像什么都没发生一样。

事实上,所有线程都是在内核空间开始的,因为 clone() 操作发生在内核空间。 (并且在您可以“返回”到用户空间中的新进程之前,还有很多内核会计工作要做。)

【讨论】:

感谢您的回复! “操作系统级线程由操作系统创建和管理。大多数语言都支持它们。(C、最近的 Java 等)”您能否指出 C 和 Java 的哪些库、结构或类提供操作系统级线程? 这是否意味着“内核级线程”在执行用户空间代码时也是“用户线程”? 内核线程不执行任何用户空间代码,因为它们不与任何用户进程关联。当用户线程进行系统调用时,它会进入内核级别。所以这取决于你的意思是“内核线程”(否)或“调用内核的线程”(是)。 围绕用户级线程/内核线程/轻量级进程/内核工作人员的术语可能是有史以来最令人困惑的事情。这些术语似乎超载到使理解差异变得非常困难的地步。你解释得很好。当我指的是由内核创建的线程来执行内核的工作时,我尝试使用内核工作线程。【参考方案4】:

从这里引用:

Kernel-Level Threads

为了降低并发性,进程的执行部分被分离到线程中。因此,操作系统现在管理线程和进程。所有线程操作都在内核中实现,操作系统调度系统中的所有线程。操作系统管理的线程称为内核级线程或轻量级进程。 NT:线程 Solaris:轻量级进程 (LWP)。

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

优点:

因为内核完全了解所有线程,所以调度程序可能决定为拥有大量线程的进程提供比拥有少量线程的进程更多的时间。 内核级线程特别适合经常阻塞的应用程序。

缺点:

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

用户级线程

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

优点:

这种技术最明显的优点是用户级线程包可以在不支持线程的操作系统上实现。 用户级线程不需要修改操作系统。 简单表示:每个线程都由一个 PC、寄存器、堆栈和一个小控制块简单表示,所有这些都存储在用户进程地址空间中。 简单管理:简单来说就是创建线程、线程间切换和线程间同步都可以在没有内核干预的情况下完成。 快速高效:线程切换并不比过程调用贵多少。

缺点:

用户级线程并不是一个完美的解决方案,就像其他一切一样,它们是一种权衡。由于用户级线程对操作系统不可见,因此它们与操作系统的集成度不高。因此,Os 可能会做出糟糕的决定,例如用空闲线程调度进程,阻塞其线程发起 I/O 的进程,即使该进程有其他线程可以运行,以及用持有锁的线程取消调度进程。解决这个问题需要内核和用户级线程管理器之间的通信。 线程和操作系统内核之间缺乏协调。因此,无论进程内有 1 个线程还是 1000 个线程,整个进程都获得一个时间片。由每个线程将控制权交给其他线程。 用户级线程需要非阻塞系统调用,即多线程内核。否则,即使进程中还有可运行的线程,整个进程也会在内核中阻塞。例如,如果一个线程导致页面错误,则进程阻塞。

【讨论】:

【参考方案5】:

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

两个独立的进程不能共享内存空间。

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

可以在单个进程中创建多个线程,这可以 共享一个公共的内存空间,独立存在于一个进程中。

现在,回到您的问题,一次一个。

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

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

- 用户级线程

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

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

用户级线程,大多会引起应用程序级的变化,内核级线程继续按照自己的节奏执行。

- 内核级线程

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

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

由于这些线程维护、执行和报告操作系统所需的进程;内核级线程的创建和管理成本更高,并且这些线程的上下文切换很慢。

大部分内核级线程不能被用户级线程抢占。

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.

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

这也许是最有趣的部分。许多用户级线程映射到内核级线程,后者又与内核通信。

一些突出的映射是:

一对一

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

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

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

多对一

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

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

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

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

多对多

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

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

缺点:内核线程的数量,如果不慎重决定会减慢系统速度。

你问题的另一部分:

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

那么,用户级线程仅仅是程序员创建的线程吗? 当然后利用内核支持的线程来执行操作 由于状态无法正常执行?

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

【讨论】:

在内核线程上“映射”用户级线程是什么意思?

以上是关于用户级线程和内核支持的线程之间的区别?的主要内容,如果未能解决你的问题,请参考以下文章

用户级线程和内核级线程的区别

用户线程和内核线程是否可以通过以下任何方式与线程级库和内核级库相对应?

线程的实现方式之内核支持线程和用户级线程

用户线程和内核线程的区别

Golang 之协程详解

操作系统: 用户级线程和内核级线程