线程上下文切换是如何完成的?

Posted

技术标签:

【中文标题】线程上下文切换是如何完成的?【英文标题】:How is thread context switching done? 【发布时间】:2011-06-01 22:40:32 【问题描述】:

据我了解,在进程上下文切换操作系统“备份”寄存器和指令指针(也是寄存器的一部分)。

但是如果在进程中的线程之间切换,操作系统会备份完整的寄存器内存和堆栈吗?

我问这个的原因是要了解Java的volatile关键字在单核处理器的情况下是否有任何意义。

【问题讨论】:

【参考方案1】:

如果 Java 的 volatile 关键字在单核处理器的情况下具有任何意义。

jit 编译器使用的优化可能会导致意外的行为。

static boolean foo = true;

public void bar()
   while(foo)
     //doSomething
     //do not modify foo in here
   

这可能会被优化,因为 foo 在循环内没有改变。

public void bar()
    while(true)
     //Now this loop never ends
     //changes to foo are ignored
    

制作 foo volatile 将告诉 jit 编译器 foo 可以被不同的线程更改并且不应该优化对它的访问。

这是有效的行为,因为跨线程访问只能保证使用

易失和同步的关键字 状态为线程安全的类(例如 java.util.concurrent.*)

更新

volatile 关键字本身不会影响上下文切换,但它会影响变量读取和写入的优化方式。这不仅会影响 cpu 缓存的使用(对多核系统很重要),还会影响及时编译器使用的优化,如上所示(对所有系统都很重要)。

【讨论】:

【参考方案2】:

了解 Java 的 volatile 关键字在单核处理器的情况下是否有意义。

这种思路是不明智的。您应该按照 API 和虚拟机的(记录的)定义进行编程。您不应该依赖某些具有特定效果或缺乏效果的东西(在这种情况下,volatile 的效果)不属于其文档定义的一部分。 即使实验表明它在特定情况下具有特定行为。因为它咬你。

【讨论】:

volatile 的行为有据可查(参见 JSR-133)。 定义明确。但我相信,它不是处理器数量。【参考方案3】:

线程切换确实意味着存储所有计算寄存器和所有堆栈,因为每个线程确实有一个单独的内存堆栈。

由于 Java 内存模型的工作方式,volatile 关键字在多线程中仍然很重要,即使在单核环境中也是如此。 volatile 变量不存储在任何类型的缓存或寄存器中,而是始终从主内存中获取,以确保每个线程始终看到变量的最新值。

【讨论】:

【参考方案4】:

是的,即使对于单核处理器,volatile 仍然有用。它告诉编译器每次读取该值时都需要从内存中重新读取(因为另一个线程可能正在更新它),而不仅仅是缓存在寄存器中。

【讨论】:

【参考方案5】:

查看JSR 133,尤其是标有“volatile 做什么?”的部分

易变字段是特殊字段 用于通信状态 线程之间。每读一个 volatile 将看到最后一次写入 任何线程的易失性;在 效果,它们被指定 程序员作为它所在的领域 永远不能接受看到“陈旧” 由于缓存或 重新排序。

这是对 volatile 如何与 JVM 内存模型一起工作的有用介绍和描述。

【讨论】:

【参考方案6】:

我知道volatile 做了什么,但我试图理解为什么这是必要的。让我详细说明。

正如上面 josefx 所提到的。如果我们有类似下面的内容:-

while(run) 
 //do something but don't modify run

如果其他线程将run 变为false,则while 循环将永远不会结束。正如 Yuval A 之前提到的,在线程上下文切换期间,寄存器也被备份。所以我猜它是这样工作的。

如果任何线程修改run 的值,该值将仅在寄存器中修改。在线程上下文切换期间,如果没有明确标记为volatile,Java 不会将此寄存器值与 RAM 中的副本同步。这样每个线程都可以使用自己的run 版本。但是这个概念不适用于非基元和整数等基元,它们不能完全放入寄存器中,迫使它们与 RAM 副本同步。

【讨论】:

以上是关于线程上下文切换是如何完成的?的主要内容,如果未能解决你的问题,请参考以下文章

如何估计线程上下文切换开销?

cpu上下文切换

Linux性能分析-CPU上下文切换

并发编程中,如何减少上下文切换

多线程上下文切换

多线程上下文切换