线程上下文切换是什么,和进程上下文切换有什么区别?(新手必看)
线程上下文切换是指在多线程操作系统中,CPU 从一个线程切换到另一个线程执行的过程。
与进程上下文切换相似,线程上下文切换涉及保存当前线程的状态(这里的“状态”指的是线程特有的信息,如线程的程序计数器、寄存器集、栈指针)并恢复另一个线程的状态的操作,以便后者可以继续执行。
线程上下文切换通常比进程上下文切换要轻量,原因在于同一进程内的线程共享相同的进程资源。因此,切换线程不需要切换内存空间和 I/O 环境,这降低了切换的开销。
但是,线程上下文切换仍然需要处理以下内容:
触发线程上下文切换的原因通常有以下几个:
在 Java 中,通过使用多线程并在其中执行一些阻塞操作或使用 sleep() 让线程睡眠,都会触发线程上下文切换。我们来看一个 Java 示例,示例代码如下:
两个线程的逻辑差异将会导致操作系统进行线程上下文切换:
注意,虽然上述示例可能会引发线程上下文切换,但实际发生切换的频率和时机取决于许多因素,包括操作系统的调度策略、系统负载、线程的优先级等。
综上所述,线程共享相同的地址空间和资源,而进程拥有独立的地址空间和资源,因此线程上下文切换是同一进程内不同线程之间的切换,涉及较少的状态变更,成本较低。而进程上下文切换是不同进程之间的切换,涉及全面的地址空间和资源的变更,成本较高。
在设计高性能应用程序时,通常会考虑使用多线程而不是多进程,以减少上下文切换的开销。
与进程上下文切换相似,线程上下文切换涉及保存当前线程的状态(这里的“状态”指的是线程特有的信息,如线程的程序计数器、寄存器集、栈指针)并恢复另一个线程的状态的操作,以便后者可以继续执行。
线程上下文切换通常比进程上下文切换要轻量,原因在于同一进程内的线程共享相同的进程资源。因此,切换线程不需要切换内存空间和 I/O 环境,这降低了切换的开销。
但是,线程上下文切换仍然需要处理以下内容:
- CPU 寄存器集:执行线程中计算的核心部分,包括程序计数器、栈指针、条件码以及通用目的寄存器等;
- 线程栈:每个线程有自己的栈,它用于存储局部变量、函数调用返回地址等。线程切换时,需要更新栈指针以反映新线程的栈状态;
- 线程特定数据(Thread-Specific Data,TSD):线程存储的其运行过程中的特有数据(如错误代码等)。
触发线程上下文切换的原因通常有以下几个:
- 多线程调度:操作系统根据线程的优先级和策略,决定哪个线程应该使用 CPU;
- 阻塞操作:如果线程执行了阻塞操作(如等待 I/O、获取无法立即获得的锁),操作系统会切换到另一个线程,以充分利用 CPU 资源;
- 时间片用尽:线程获得的 CPU 资源是有限的。当时间片用尽,操作系统会进行上下文切换,让另一个线程运行;
- 高优先级线程就绪:当一个高优先级线程从阻塞状态切换为就绪状态时,操作系统可能会打断当前线程,切换到高优先级线程运行。
在 Java 中,通过使用多线程并在其中执行一些阻塞操作或使用 sleep() 让线程睡眠,都会触发线程上下文切换。我们来看一个 Java 示例,示例代码如下:
public class ThreadSwitchExample { public static void main(String[] args) { // 创建并启动一个计算线程 Thread computationThread = new Thread(() -> { long sum = 0; for(long i = 0; i < 1000000L; i++) { sum += i; // 每计算一定次数,线程休眠一小段时间,增加上下文切换的可能性 if(i%1000 == 0) { try { Thread.sleep(1); } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 重新设置中断状态 System.out.println("Computation thread was interru pted."); return; } } } System.out.println("Computation finished: " + sum); }); // 创建并启动一个等待用户输入的线程 Thread inputThread = new Thread(() -> { try { System.out.println("Waiting for user input:"); int read = System.in.read(); // 线程执行阻塞操作,等待用户输入 System.out.println("You entered: " + (char) read); } catch (IOException e) { System.out.println("An error occurred while reading input."); } }); // 启动线程 computationThread.start(); inputThread.start(); // 等待两个线程完成 try { computationThread.join(); inputThread.join(); } catch (InterruptedException e) { System.out.println("Main thread was interrupted."); } System.out.println("Main thread finished."); } }在上述代码中,main() 方法主线程启动了两个线程:
- 一个是 computationThread 线程(用于进行计算);
- 另一个是 inputThread 线程(用于等待用户输入)。
两个线程的逻辑差异将会导致操作系统进行线程上下文切换:
- computationThread 在计算过程中会间歇性休眠,休眠是通过 sleep(1) 方法实现的。这种休眠会使得操作系统有机会将 CPU 资源分配给其他的线程,从而引发线程上下文切换;
- inputThread 使用 System.in.read() 方法执行阻塞操作直到接收到输入。在该线程等待用户输入期间,操作系统可能会将 CPU 资源分配给其他线程。
注意,虽然上述示例可能会引发线程上下文切换,但实际发生切换的频率和时机取决于许多因素,包括操作系统的调度策略、系统负载、线程的优先级等。
综上所述,线程共享相同的地址空间和资源,而进程拥有独立的地址空间和资源,因此线程上下文切换是同一进程内不同线程之间的切换,涉及较少的状态变更,成本较低。而进程上下文切换是不同进程之间的切换,涉及全面的地址空间和资源的变更,成本较高。
在设计高性能应用程序时,通常会考虑使用多线程而不是多进程,以减少上下文切换的开销。