Golang 协程开销测试

在 Linux 中进程和线程的上下文切换开销,大约是 3-5us 之间。但是在高并发场景下,每秒钟数千万请求,即使 3-5us 的开销,如果上下文切换量特别大的话,仍然会显得性能低下。为避免频繁的上下文切换,有一种异步非阻塞的开发模型,就是用一个进程或线程去接受一大堆用户的请求,然后通过 IO 多路复用的方式来提高性能(进程或线程不阻塞,省去了上下文切换的开销)。Nginx 和 Node.js 就是这种模型的典型代表产品。但是这种编程模型的问题在于对开发不友好,于是在这个基础上就设计出了不需要进程/线程上下文切换的“线程”,协程。用协程去处理高并发的应用场景,既能够让开发者使用正常思维处理业务,也能够省去昂贵的进程/线程上下文切换开销。因此,协程是 Linux 处理海量请求应用场景里的进程模型的一个很好的补丁。

我们知道协程的封装虽然轻量,但是还是需要引入一些额外的开销的,下面我们来测试一下这个开销具体是多少吧。

协程切换 CPU 开销

测试过程是不断在协程直接让出 CPU。核心代码如下:

func cal() {
	for i := 0; i < 1000000; i++ {
		// 让当前的 goroutine 让出 CPU
		// 好让其他的 goroutine 获得执行机会
		// 同时当前的 goroutine 也会在未来某个时间点继续执行
		runtime.Gosched()
	}
}

func main() {
	// 设置逻辑 CPU 数量
	// 如果在线程模型中,开发者需要维护线程池中线程与 CPU 核心数的对应关系
	// 在 Go 中只需要用 runtime.GOMAXPROCS() 就行
	// Go 运行时实现了一个小型任务调度器
	// 可以高效地将 CPU 资源分配给每一个任务
	// 另外还可以使用 runtime.NumCPU() 查询逻辑 CPU 核心数
	runtime.GOMAXPROCS(1)
	// time.Now() 获取当前时间
	// 2022-02-03 11:49:55.993545 +0800 CST m=+0.000074543
	// time.Now().Unix() 获取单位为秒的时间戳
	// time.Now().UnixNano() 获取单位为纳秒的时间戳
	// 如果想要格式化时间,可以用
	// time.Now().Format("2006-01-02 15:04:05")
	// 2006-01-02 15:04:05 固定写法,据说是 Golang 的诞生时间
	currentTime := time.Now().UnixNano()
	go cal()
	cal()
	currentTime = time.Now().UnixNano() - currentTime
	fmt.Println("===", currentTime)
}

平均每次协程切换的开销是 120ns 左右,相对于进程切换的开销大约 3.5us,大约是其的三十分之一,比系统调用造成的开销还要低。

协程内存开销

在空间上,协程初始化创建的时候为其分配的栈有 2KB,而线程栈要比这更数字大的多。可以使用 ulimit -a 命令查看,一般都在几兆,例如本人的机器是 8M:

 ~ ulimit -a
-t: cpu time (seconds)              unlimited
-f: file size (blocks)              unlimited
-d: data seg size (kbytes)          unlimited
-s: stack size (kbytes)             8176

如果对每个用户创建一个协程去处理,100 万并发请求只需要 2G 内存就够了,而如果用线程模型则需要 10T。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值