关于阻塞 "peer threads" 当用户级线程阻塞时的混淆

Confusion regarding the Blocking of "peer threads" when a user-level thread blocks

我正在阅读有关线程和进程之间的差异的信息,网上几乎到处都是,通常只写一个差异而没有太多解释:

If a process gets blocked, remaining processes can continue execution. If a user level thread gets blocked, all of its peer threads also get blocked.

这对我来说没有任何意义。如果调度程序无法在阻塞线程和 ready/runnable 线程之间切换,那么并发意义是什么?给出的原因是,由于 OS 不区分给定父进程的各个线程,它会立即阻塞所有线程。

我觉得这很难令人信服,因为所有现代 OS 都有带有线程 ID 的线程控制块,即使它只在父进程的内存 space 中有效。就像 Galvin 的操作系统书中给出的示例一样,如果拼写检查线程无法连接到某个在线词典,我可能不希望处理我的输入的线程被阻止。

要么我对这个概念的理解有误,要么所有这些网站都只是复制了多年来的一些旧线程差异。此外,我在书籍中找不到这种说法,例如 Galvin 的或者 William Stalling 的 COA 书中讨论了线程。

这些是我找到语句的资源:

内核级和用户级线程之间存在差异。简单来说:

  • 内核级线程:由操作系统管理的线程,包括调度。它们是在处理器上执行的。这可能是我们大多数人对线程的看法。
  • 用户级线程:由程序本身管理的线程。在某些情况下,它们也被称为纤程或协程。与内核级线程不同,它们需要“放弃执行”,即从一个用户级线程切换到另一个用户级线程是由程序明确完成的。 用户级线程映射到内核级线程。

由于用户级线程需要映射到内核级线程,所以需要选择合适的映射。您可以将每个用户级别映射到一个单独的内核级线程。您还可以将许多用户级映射到一个内核级线程。在后一种映射中,您让多个并发执行路径由“我们所知”的单个线程执行。如果这些路径之一阻塞,回想一下用户级线程需要放弃执行,然后执行(内核级)线程阻塞,这导致所有其他分配的路径也被有效地阻塞。我认为,这就是声明所指的内容。 仅供参考: 在 Java 中,用户级线程——您在程序中执行的多线程——由 JVM(即运行时系统)映射到内核级线程。


相关资料:

至少在 Java 的早期,用户级线程被称为“绿色线程”,以在不支持本机线程的操作系统上实现 Java 线程。还有一篇 Wiki 文章 https://en.wikipedia.org/wiki/Green_threads 解释了来源和含义。

(那时候 desktops/laptops 是单处理器系统,在它们的 1 个物理插槽中有一个单核 CPU,而 SMP 机器大多只作为多插槽存在。)

你说得对,这太糟糕了,一旦主流操作系统发展到支持本机线程,大多数人就不再这样做了。至少对于 Java 而言,Green threads 指的是编程语言 Java 的原始线程库的名称(在 1.1 版中发布,然后是 Green threads 在版本 1.3 中被放弃为本地线程)。

因此,如果您不希望拼写检查线程阻塞整个应用程序,请使用 Java 版本 1.3 或更高版本。 :P 这是古老的历史。

虽然当系统调用returns它会阻塞时有一定的使用非阻塞IO和上下文切换的范围,但通常最好让内核处理线程阻塞和解除阻塞,所以就是这样普通的现代系统可以。


IIRC 在 Solaris 上也有一些使用 N:M 模型,其中 N 个用户-space 线程可能由少于 N 个内核线程处理。这可能意味着有一些“对等”线程(共享相同的内核线程)就像在你的引述中一样,而不是完全糟糕的纯粹用户space绿色线程。

(即您的总线程中只有 一些 共享同一个内核线程。)

Linux 上的 pthreads 使用 1:1 模型,其中每个软件线程都是一个单独的任务,供内核调度。

Google 发现 https://flylib.com/books/en/3.19.1.51/1/ 定义了那些线程模型并讨论了一些,包括 N:M 混合模型和 N:1 user-space aka 绿色线程模型,如果它想避免阻塞其他线程,则需要使用非阻塞 I/O。 (例如,如果系统调用 returns EAGAIN 或在排队异步读取或写入之后执行 user-space 上下文切换。)

好的,其他回答都提供了详细的信息

但是要在中间击中你的主要转换:

  • 这篇文章有点错误,缺乏必要的上下文(请参阅@akuzminykh 对用户级线程和内核级线程的解释中的所有详细信息)
  • 这对 Java 程序员意味着什么:不要理会那些解释。如果您的 Java 线程之一阻塞(由于 I/O 等),那将不会对您的任何其他线程产生影响(当然,除非您明确希望它们这样做,但是您会必须为此明确使用机制)

线程如何在 Java 中被阻塞?

  • 如果您调用 sleep()wait() 等,当前执行该代码的线程(不是您调用它们的对象)将被阻塞。这些将在某些事件中释放:一旦计时器 运行 结束或线程被另一个线程中断,睡眠将完成,等待将在另一个线程通知后释放。
  • 如果您 运行 进入 synchronized(lockObj) 块或方法:一旦占用 lockObj 的其他线程释放它,这将释放它
    • 与此密切相关,如果您输入 ThreadGates、互斥锁等,所有这些 1000 多个专门的 类 用于扩展线程控制,如会合等
  • 如果您调用阻塞 I/O 方法,例如从 InputStream 等阻塞读取:int amountOfBytesRead = read(buffer, offset, length),或 String line = myBufferedReader.readLine();
    • 与此相反,有许多非阻塞I/O操作,像大多数java.nio(非阻塞I/O)包一样,return立即, 但可能表示无效的结果值
  • 如果垃圾收集器执行快速清理周期(通常很短,您甚至不会注意到,并且线程会再次自动释放)
  • 如果您为流上某些持久的 lambda 函数调用 .parallelStream() 函数(如 myList.parallelStream().forEach(myConsumerAction)),如果太复杂或元素太多,则由自动多线程机制处理(这你不会注意到,因为在完成所有事情之后,你的调用线程将恢复正常,就像调用了一个普通方法一样)。在此处查看更多信息:https://www.baeldung.com/java-when-to-use-parallel-stream