当前位置: 首页 > news >正文

线程和协程的区别了解

1.资源消耗

  • 调度方式:线程由操作系统内核调度(抢占式),协程由程序自己控制调度(协作式)。
  • 切换开销:线程切换涉及内核态与用户态的转换,开销大;协程只在用户态切换上下文,开销小。
  • 状态保存:线程保存寄存器、栈、指令指针等完整上下文;协程只保存少量栈、程序计数器等上下文。
  • 资源隔离: 各线程有自己的堆栈,数据隔离;同一进程内协程共享内存,需注意同步。

线程创建 & 销毁

  • 创建:需要申请内核资源(线程栈、PCB 等),大约消耗 几 MB 内存

  • 销毁:回收内核资源,代价也不小

  • 上下文切换:涉及用户态/内核态切换,保存寄存器、栈、内存页表,切换一次可能 耗时 1000ns+

协程创建 & 销毁

  • 创建:只需要申请一小段栈空间(一般几 KB),几乎不涉及内核资源

  • 销毁:内存回收代价极小

  • 上下文切换:只保存少量寄存器和栈,切换一次只需 几十纳秒(几乎不感知)。可以理解为用户态的调度器。

2.适用情况

👉 总结一句话

  • 线程是“重装战士”,适合 多核并行 + CPU 密集型任务

  • 协程是“敏捷刺客”,适合 高并发 IO 密集型任务

3.内核态和用户态

🔍 线程为什么开销高

  • 用户态 ↔️ 内核态:涉及 系统调用syscall),需要 切换模式,如 read()write() 都要从用户态陷入内核态。涉及到操作系统调用。

  • 内核栈切换:每个线程都有独立内核栈,切换需要重新加载。

  • 缓存失效:切换线程可能导致 CPU 缓存失效,重建 TLB(地址翻译缓存)也增加延迟。

💥 开销对比

  • 线程切换(内核调度):大约 1000ns+

  • 进程切换(内核调度 + 页表切换):大约 几微秒

  • 协程切换(用户态调度):大约 几十纳秒

4.代码例子

chatgpt给出的切换线程和协程的耗时,在数量都是1w的情况下,切换协程的耗时居然比较高,数量如果达到10w,是线程切换的耗时比较高。

package main

import (
	"fmt"
	"runtime"
	"sync"
	"time"
)

const (
	numGoroutines = 100000
	numThreads    = 100000
)

// 模拟一个消耗时间的任务
func doTask(id int, ch chan int, wg *sync.WaitGroup) {
	defer wg.Done()
	// 模拟一些计算任务
	for i := 1; i < 1000; i++ {
		_ = (i * i) % id
	}
	ch <- id
}

// 记录线程切换时间
func measureThreadSwitching() {
	start := time.Now()
	var wg sync.WaitGroup
	for i := 1; i < numThreads; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			// 模拟一个计算任务
			_ = id * id
		}(i)
	}
	wg.Wait()
	elapsed := time.Since(start)
	fmt.Printf("Thread switching took: %v\n", elapsed)
}

// 记录协程切换时间
func measureGoroutineSwitching() {
	start := time.Now()
	var wg sync.WaitGroup
	ch := make(chan int, numGoroutines)
	for i := 1; i < numGoroutines; i++ {
		wg.Add(1)
		go doTask(i, ch, &wg)
	}
	wg.Wait()
	elapsed := time.Since(start)
	fmt.Printf("Goroutine switching took: %v\n", elapsed)
}

func main() {
	// 设置 Go 运行时使用多个 CPU 核心
	runtime.GOMAXPROCS(runtime.NumCPU())

	// 测量线程切换的时间
	fmt.Println("Measuring thread switching...")
	measureThreadSwitching()

	// 测量协程切换的时间
	fmt.Println("Measuring goroutine switching...")
	measureGoroutineSwitching()
}

相关文章:

  • C++ Reference:解锁编程新姿势
  • Flutter TextField 从入门到精通:掌握输入框的完整指南
  • 3月22日星期六今日早报简报微语报早读
  • 四层板入门-stm32C8T6最小系统板
  • 2024 浅浅总结
  • Java学习总结-泛型
  • SpringMVC初始化原理剖析和源码跟踪
  • RTD2525BE《HDMI转EDP,DP转EDP》显示器芯片
  • 笔记:代码随想录算法训练营day59:110.字符串接龙 、105.有向图的完全可达性、106.岛屿的周长
  • linux中如何修改文件的权限和拥有者所属组
  • Web纯前端实现在线打开编辑保存PPT幻灯片
  • 第四章:ESP32零基础教学 - 4.2继电器、舵机与L298N电机
  • web客户端存储,IndexDB相关讲解
  • 【实战指南】用MongoDB存储文档和图片等大文件(Java实现)
  • 破解内存瓶颈:如何通过内存池优化资源利用
  • 【算法day18】有效的括号——给定一个只包括 ‘(‘,‘)‘,‘{‘,‘}‘,‘[‘,‘]‘ 的字符串 s ,判断字符串是否有效。
  • 【第14届蓝桥杯C/C++B组省赛】01串的熵
  • PCA Jittering 图像增强
  • 【通过Groovy去热修复线上逻辑】1.执行线上数据修复 2.写工具
  • 仿函数 VS 函数指针实现回调
  • 国内生产、境外“游一圈”再进保税仓,这些“全球购”保健品竟是假进口
  • 洗冤录·巴县档案|道咸年间一起家暴案
  • 宜家上海徐汇商场明天恢复营业,改造后有啥新变化?
  • 谁将主导“视觉大脑”?中国AI的下一个超级赛道
  • 经济日报金观平:充分发挥增量政策的经济牵引力
  • 迎接神十九乘组回家,东风着陆场各项工作已准备就绪