当前位置: 首页 > Linux

与线程相比,协程可以节省多少开销?

时间:2023-04-07 01:09:51 Linux

在上一篇文章中,我们通过实验验证了Linux进程和线程的上下文切换开销,大约是3-5us。在正常的计算机程序上运行时,这个开销确实没有那么大。但是,与一般的计算机程序相比,海量互联网服务器具有以下特点:高并发:每秒需要处理数千个用户周期短:每个用户的处理时间越短越好,往往在ms级别高网络IO:经常需要从其他机器进行网络IO,如Redis、Mysql等。低计算:一般CPU密集型计算操作不多。即使3-5us的开销太大,还是会显得有些低效。比如之前的WebServerApache就是这种模式下的软件产品。(其实当时设计linux操作系统的时候,目标是一个通用的操作系统,并不是专门为服务端的高并发设计的。)为了避免频繁的上下文切换,有也是一种异步和非阻塞的开发模型。即用一个进程或线程来接收大量的用户请求,然后通过IO多路复用来提高性能(进程或线程不阻塞,免去上下文切换的开销)。Nginx和NodeJs就是这种模式的典型代表产品。平心而论,这种模式是最机器友好的,在程序运行效率上也是运行效率最高的(优于下文提到的协程开发模式)。所以Nginx已经取代Apache成为WebServer的首选。但是这种编程模型的问题是对开发不友好。说白了就是太机器化了,这与抽象进程概念的初衷背道而驰。人类正常的线性思维被打乱,应用层开发者被迫用非人的思维编写代码,代码调试变得异常困难。于是一些聪明的脑袋继续在应用层动脑筋,设计了不需要进程/线程上下文切换的“线程”和协程。使用协程来应对高并发的应用场景,既可以满足流程的初衷,又可以让开发者用正常人的线性思维来处理自己的业务,还可以节省昂贵的流程/线程上下文切换的成本.因此,可以说协程在Linux处理海量请求的应用场景中,是进程模型的一个很好的补丁。背景介绍到此结束,那么我想说的是,毕竟协程的封装虽然是轻量级的,但是还是需要引入一些额外的开销。那么让我们来看看这些额外成本有多小。协程开销测试1.协程切换CPU开销测试代码如下。测试过程就是在协程之间不断让出CPU。核心代码如下:funccal(){fori:=0;i<1000000;i++{runtime.Gosched()}}funcmain(){runtime.GOMAXPROCS(1)currentTime:=time.Now()fmt.Println(currentTime)gocal()fori:=0;i<1000000;i++{runtime.Gosched()}currentTime=time.Now()fmt.Println(currentTime)}好的,让我们编译运行:#cdtests/test05/src/main/;#gobuild#./main2019-08-0822:35:13.415197171+0800CSTm=+0.0002860592019-08-0822:35:13.655035993+0800CSTm=+0.240124923average每个协程切换的开销为(6550359-415197171)/2000000=120ns。与上一篇测得的进程切换开销相比,大约是3.5us,大约是它的三十分之一。系统调用带来的开销更低。2.协程的内存开销是在空间方面。初始化和创建时为协程分配的堆栈为2KB。线程栈远大于这个数,可以通过ulimit命令查看。一般是几兆,笔者的机器是10M。如果为每个用户创建一个协程来处理,100万并发用户请求只需要2G内存,如果使用线程模型,则需要10T。#ulimit-astacksize(kbytes,-s)10240本节的结论是协程在用户态完成上下文切换,所以切换只需要100ns多一点,比进程切换长30倍。单个协程所需的栈内存也足够小,只有2KB。因此,近几年协程开始流行起来,在互联网后端的高并发场景中大放异彩。无论是空间性能还是时间性能都比进程(线程)好那么多,那么Linus为什么不在操作系统中实现呢?其实协程并不是什么新鲜事物,它在1960年代就已经被提出来了。操作系统的主要设计目标之一是实时性,优先级高的进程会抢占当前占用CPU的进程。但协程无法做到这一点,必须依赖使用CPU的一方主动释放,这与操作系统的目的不符。协程的效率是以抢占为代价的。延伸:因为go的协程实在是调用起来太方便了,有的go程序员就是随便go一下。要知道在切换到协程之前,go指令必须先创建一个协程。而一次创建加上调度开销会上升到400ns,几乎相当于一次系统调用的耗时。协程虽然效率很高,但也不要乱用,否则go之祖RobPike优化过的性能,就被你随便一个go给毁了。练内功专用CPU:1、你以为你的多核CPU是真核生物吗?多核“错觉”2、听说你只知道内存,不知道缓存?CPU表示很伤心!3.TLB缓存是个鬼,如何查看TLBmiss?4、进程/线程切换需要多少开销?5、协程比线程好在哪里?6.softirq会吃掉多少CPU?7、一次系统调用的开销是多少?8、一个简单的php请求redis的开销是多少?9、函数调用过多会不会有性能问题?我的公众号是“练内功练功”。在这里,我不是简单地介绍技术理论,也不是只介绍实践经验。而是理论联系实际,用实践加深对理论的理解,用理论提高技术实践能力。欢迎关注我的公众号,分享给你的朋友吧~~~