我一直在浏览基于这个主题的一些笔记,虽然我对线程有一般的了解,但我并不确定用户级线程和内核级线程之间的区别。
我知道进程基本上是由多个线程或单个线程组成的,但是这些线程是前面提到的两种类型吗?
据我了解,内核支持的线程可以访问内核以进行系统调用和其他用户级线程不可用的用途。
那么,用户级线程是否只是程序员创建的线程,然后利用内核支持的线程来执行由于其状态而无法正常执行的操作?
我一直在浏览基于这个主题的一些笔记,虽然我对线程有一般的了解,但我并不确定用户级线程和内核级线程之间的区别。
我知道进程基本上是由多个线程或单个线程组成的,但是这些线程是前面提到的两种类型吗?
据我了解,内核支持的线程可以访问内核以进行系统调用和其他用户级线程不可用的用途。
那么,用户级线程是否只是程序员创建的线程,然后利用内核支持的线程来执行由于其状态而无法正常执行的操作?
编辑:这个问题有点令人困惑,所以我以两种不同的方式回答它。
为了清楚起见,我通常说“操作系统级线程”或“本机线程”而不是“内核级线程”(我在下面的原始答案中与“内核线程”混淆了。)操作系统级线程由创建和管理操作系统。大多数语言都支持它们。(C、最近的 Java 等)它们非常难以使用,因为您 100% 负责防止出现问题。在某些语言中,即使是本机数据结构(例如哈希或字典)也会在没有额外锁定代码的情况下破坏。
与操作系统线程相反的是由您的语言管理的绿色线程。这些线程根据语言被赋予不同的名称(C 中的协程、Go 中的 goroutine、Ruby 中的纤维等)。这些线程只存在于您的语言中,而不存在于您的操作系统中。因为语言选择上下文切换(即在语句末尾),它可以防止大量微妙的竞争条件(例如看到部分复制的结构,或者需要锁定大多数数据结构)。程序员看到“阻塞”调用(即data = file.read()
),但语言将其转换为对操作系统的异步调用。然后该语言允许其他绿色线程在等待结果时运行。
绿色线程对程序员来说要简单得多,但它们的性能各不相同:如果你有很多线程,绿色线程对于 CPU 和 RAM 都可能更好。另一方面,大多数绿色线程语言不能利用多核。(您甚至不能再购买单核计算机或手机了!)。一个糟糕的库可以通过阻塞操作系统调用来停止整个语言。
两全其美的是每个 CPU 有一个 OS 线程,以及许多神奇地移动到 OS 线程上的绿色线程。Go 和 Erlang 等语言可以做到这一点。
系统调用和其他用户级线程不可用的用途
这只是对了一半。是的,如果你自己调用操作系统(即做一些阻塞的事情),你很容易引起问题。但是语言通常有替换,所以你甚至不会注意到。这些替换确实调用了内核,只是与您想象的略有不同。
编辑:这是我最初的答案,但它是关于用户空间线程与仅内核线程的,这(事后看来)可能不是问题。
用户线程和内核线程完全相同。(您可以通过查看 /proc/ 看到内核线程也在那里。)
用户线程是执行用户空间代码的线程。但它可以随时调用内核空间。它仍然被认为是一个“用户”线程,即使它以更高的安全级别执行内核代码。
内核线程是仅运行内核代码且与用户空间进程无关的线程。这些类似于“UNIX 守护程序”,只是它们是仅内核的守护程序。所以你可以说内核是一个多线程程序。例如,有一个用于交换的内核线程。这会强制所有交换问题“序列化”到单个流中。
如果用户线程需要某些东西,它会调用内核,从而将该线程标记为休眠。稍后,交换线程找到数据,因此将用户线程标记为可运行。再后来,“用户线程”从内核返回到用户空间,就好像什么都没发生一样。
事实上,所有线程都从内核空间开始,因为 clone() 操作发生在内核空间。(在你可以“返回”到用户空间中的新进程之前,还有很多内核会计要做。)
在进行比较之前,让我们先了解一下线程是什么。线程是独立进程域中的轻量级进程。它们是必需的,因为流程繁重,消耗大量资源,更重要的是,
两个独立的进程不能共享一个内存空间。
假设您打开一个文本编辑器。它是一个在内存中执行的独立进程,具有单独的可寻址位置。在此过程中您将需要许多资源,例如插入图形、拼写检查等。为这些功能中的每一个创建单独的过程并在内存中独立维护它们是不可行的。为了避免这种情况,
可以在一个进程中创建多个线程,这些线程可以共享一个公共的内存空间,在一个进程中独立存在。
现在,回到你的问题,一次一个。
我不太确定用户级线程和内核级线程之间的区别。
线程根据执行域大致分为用户级线程和内核级线程。也有 一个或多个用户线程映射到一个或多个内核线程的情况。
- 用户级线程
用户级线程主要位于应用程序级别,应用程序创建这些线程以维持其在主存储器中的执行。除非需要,否则这些线程与内核线程隔离工作。
这些更容易创建,因为它们不必引用许多寄存器,并且上下文切换比内核级线程快得多。
用户级线程,大多会导致应用程序级的变化,内核级线程继续按照自己的节奏执行。
- 内核级线程
这些线程大多独立于正在进行的进程,由操作系统执行。
操作系统需要这些线程来执行内存管理、进程管理等任务。
由于这些线程维护、执行和报告操作系统所需的进程;内核级线程的创建和管理成本更高,并且这些线程的上下文切换很慢。
大多数内核级线程不能被用户级线程抢占。
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.
- 映射到内核线程的用户级线程
这也许是最有趣的部分。许多用户级线程映射到内核级线程,后者又与内核通信。
一些突出的映射是:
一对一
当一个用户级线程只映射到一个内核线程时。
优点:每个用户线程映射到一个内核线程。即使其中一个用户线程发出阻塞系统调用,其他进程也不受影响。
缺点:每个用户线程都需要一个内核线程进行交互,并且内核线程的创建和管理成本很高。
多对一
当许多用户线程映射到一个内核线程时。
优点:不需要多个内核线程,因为类似的用户线程可以映射到一个内核线程。
缺点:即使一个用户线程发出阻塞系统调用,映射到该内核线程的所有其他用户线程都被阻塞。
此外,由于内核一次只能处理一个内核线程,因此无法实现良好的并发性。
多对多
当许多用户线程映射到相等或更少数量的内核线程时。程序员决定将多少用户线程映射到多少内核线程。一些用户线程可能只映射到一个内核线程。
优点:实现了很高的并发性。程序员可以决定一些可能发出阻塞系统调用的潜在危险线程,并将它们与一对一映射一起放置。
缺点:内核线程的数量,如果不慎决定会减慢系统速度。
你问题的另一部分:
内核支持的线程可以访问内核以进行系统调用和其他用户级线程不可用的用途。
那么,用户级线程是否只是程序员创建的线程,然后利用内核支持的线程来执行由于其状态而无法正常执行的操作?
部分正确。几乎所有内核线程都可以访问系统调用和其他关键中断,因为内核线程负责执行操作系统的进程。用户线程将无法访问其中一些关键功能。例如,文本编辑器永远无法触发能够更改进程物理地址的线程。但是如果需要,用户线程可以映射到内核线程并发出一些它不能作为独立实体执行的系统调用。然后,内核线程将此系统调用映射到内核,并在认为合适的情况下执行操作。
一些开发环境或语言会添加自己的线程之类的特性,这是为了利用环境的一些知识而编写的,例如,GUI 环境可以实现一些线程功能,这些功能在每个事件循环上的用户线程之间切换。
游戏库可能有一些类似于角色的线程行为。有时,类似用户线程的行为可以以不同的方式实现,例如我经常使用可可,它有一个计时器机制,每 x 秒执行一次你的代码,使用几分之一秒,它就像一个线程。Ruby 有一个类似于协作线程的 yield 特性。用户线程的优点是它们可以在更可预测的时间切换。对于内核线程,每次线程再次启动时,它都需要加载它正在处理的任何数据,这可能需要时间,对于用户线程,您可以在完成处理某些数据时切换,因此不需要重新加载。
我没有遇到过看起来与内核线程相同的用户线程,只有像计时器这样的线程机制,尽管我在较旧的教科书中读过它们,所以我想知道它们是否是过去更流行但与真正的多线程操作系统(现代 Windows 和 Mac OS X)和更强大的硬件的兴起我想知道它们是否已经失宠。
从这里引用:
为了使并发更便宜,进程的执行方面被分离到线程中。因此,操作系统现在管理线程和进程。所有线程操作都在内核中实现,操作系统调度系统中的所有线程。操作系统管理的线程称为内核级线程或轻量级进程。NT:线程 Solaris:轻量级进程 (LWP)。
在这种方法中,内核知道并管理线程。在这种情况下不需要运行时系统。内核有一个线程表来跟踪系统中的所有线程,而不是每个进程中的线程表。此外,内核还维护着传统的进程表来跟踪进程。操作系统内核提供系统调用来创建和管理线程。
好处:
因为内核对所有线程有充分的了解,调度程序可能决定给拥有大量线程的进程比拥有少量线程的进程更多的时间。内核级线程特别适用于经常阻塞的应用程序。
缺点:
内核级线程缓慢且效率低下。例如,线程操作比用户级线程慢数百倍。由于内核必须管理和调度线程和进程。每个线程都需要一个完整的线程控制块 (TCB) 来维护有关线程的信息。因此,存在大量开销并增加了内核复杂性。
用户级线程
内核级线程使并发比进程便宜得多,因为分配和初始化的状态要少得多。但是,对于细粒度的并发,内核级线程仍然承受着过多的开销。线程操作仍然需要系统调用。理想情况下,我们要求线程操作与过程调用一样快。内核级线程必须是通用的,以支持所有程序员、语言、运行时等的需求。对于这种细粒度的并发,我们仍然需要“更便宜”的线程。为了使线程既便宜又快速,它们需要在用户级别实现。用户级线程完全由运行时系统(用户级库)管理。内核对用户级线程一无所知,并像单线程进程一样管理它们。用户级线程小而快,每个线程由一台 PC 表示,寄存器、堆栈和小线程控制块。创建一个新线程、在线程之间切换和同步线程都是通过过程调用完成的。即没有内核参与。用户级线程比内核级线程快一百倍。
好处:
这种技术最明显的优点是用户级线程包可以在不支持线程的操作系统上实现。用户级线程不需要修改操作系统。简单表示:每个线程都由一个 PC、寄存器、堆栈和一个小控制块简单表示,所有这些都存储在用户进程地址空间中。简单管理:简单来说就是创建线程、线程间切换和线程间同步都可以在没有内核干预的情况下完成。快速高效:线程切换并不比过程调用贵多少。
缺点:
用户级线程并不是一个完美的解决方案,就像其他一切一样,它们是一种权衡。由于用户级线程对操作系统不可见,因此它们与操作系统的集成度不高。因此,Os 可能会做出糟糕的决定,例如用空闲线程调度进程、阻塞其线程发起 I/O 的进程,即使该进程有其他线程可以运行,以及用持有锁的线程取消调度进程。解决这个问题需要内核和用户级线程管理器之间的通信。线程和操作系统内核之间缺乏协调。因此,无论进程内有 1 个线程还是 1000 个线程,整个进程都获得一个时间片。由每个线程将控制权交给其他线程。用户级线程需要非阻塞系统调用,即 多线程内核。否则,即使进程中还有可运行的线程,整个进程也会在内核中阻塞。例如,如果一个线程导致页面错误,则进程阻塞。