logo头像
Snippet 博客主题

用协程代替线程,减少上下文切换

本文于 870 天之前发表,文中内容可能已经过时。

背景

在开发工作中,遇到一些串行处理比较耗时的任务,我会想到用多线程,切片并行执行,已减少请求响应时间,提高吞吐量。可是,开辟线程也是需要消耗系统内存的,一个线程的线程栈,JVM默认配置1M左右,线程栈的内存的上限 只受操作系统的内存限制,所以线程创建过多,会在JVM的LOG日志看到这一行

java.lang.OutOfMemoryError: unable to create new native thread

,通常我们使用的线程池来管理和监控线程,使用线程池注意的地方参考线程池容易被忽略的地方

话说回来,我们创建线程,就能提高性能吗?创建线程数一般根据CPU的个数来设置的。**一个CPU只会同时支持一个线程运行,CPU根据分配的时间片(单位几十MS)运行该线程,到了这个时间片后,有二种情况:

  1. 这个线程任务执行完了,则释放CPU
  2. 这个线程的任务还没有执行完,则CPU也会强行释放服务其他线程了,并将该线程塞到等待队列等待下次调度,在很短的时间后该线程又获得了CPU调度,CPU根据上次的状态(对应JVM的谈到的程序计数器),继续执行下去,这一个周期叫做上下文切换**,由此可见CPU上下文切换影响线程执行效率。

如何定量分析上下文切换次数呢?

Linux提供了性能检测工具,使用vmstat可以测量上下文切换的次数。

root@ubuntu:~# vmstat 2 1
  • 第一个参数是采样的时间间隔数,单位是秒
  • 第二个参数是采样的次数

运行后有如下参数:

 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us 

r
表示运行队列(就是说多少个进程真的分配到CPU),当这个值超过了CPU数目,就会出现CPU瓶颈了。如果运行队列过大,表示你的CPU很繁忙,一般会造成CPU使用率很高。

cs
每秒上下文切换次数,线程的切换,也要进程上下文切换,这个值要越小越好,太大了,要考虑调低线程或者进程的数目,上下文切换次数过多表示你的CPU大部分浪费在上下文切换,导致CPU干正经事的时间少了,CPU没有充分利用,是不可取的。

那么如何减少上下文切换呢?

  • 可以想到的是,增加CPU,当然增加了硬件的成本,老板不同意的。
  • 另一个容易就是想到的,减少线程数,这个只能视业务而定。
  • 再一个就是无锁编程,锁会增大线程上下文切换的次数。
  • 还有一个该提到的就是CAS,JDK里原子类和并发包都有应用。
  • 最后提到的是,用协程代替线程

那么什么是协程呢?

协程跟线程的用法相似,对于我们JAVA党不熟悉,但在golang,kotlin 1.30,python:都有原生的支持了。具体用法参考聊一聊 Java-协程 那些事,我目前的水平还达不到解说,值得提的是如下几点:

对于那些需要CPU长时间计算的代码,很少遇到阻塞的时候,就应该首选thread。
当代码经常会被等待其它资源的阻塞的时候,就应该使用协程

附录

操作系统——CPU调度
从 synchronized 到 CAS 和 AQS - 彻底弄懂 Java 各种并发锁
Linux vmstat命令实战详解