如何正确看待IO密集型应用的并发编程

前言

以下主要从Java开发者的角度出发,主要阐述了个人在学习JavaWeb以及WebServer、Java多线程后的一些感想,错误认知在所难免,大家可以一起讨论。

CPU密集型应用

对于CPU密集型应用,性能瓶颈在CPU,此时不需要设置过多线程,只需要设置核数个线程即可,跑满CPU就是最大化效率,过多线程会导致线程切换,降低CPU利用率。

IO密集型应用

而Web应用等属于IO密集型应用,性能瓶颈基本集中于数据库IO。在数据库性能一定的情况下,应用服务器需要支持尽可能多的并发请求,其实就是在内存一定的情况下支持更多并发的用户请求进入系统。但是一味增加线程数也是不切实际的,会导致线程切换频繁,CPU的开销增大,反而降低效率。

阻塞IO是指进入IO后当前线程进入等待态,等待IO返回,由OS执行线程调度。

而异步IO是指进入IO后当前线程并不会进入等待态,可以继续执行其他代码,IO执行完毕触发回调,此时继续分配线程,执行后续任务。

  • 对于Tomcat等基于request per thread(NIO+线程池)服务器,往往会设置有着几百个线程的线程池。线程可以在开始阻塞IO时进入等待态,由OS执行线程调度,在IO完毕后线程重新回到就绪态,此时OS会给该线程分配时间片。而支持尽可能多的并发请求的关键,在于每个线程无法做到快速回收,必须等待IO完毕后才会回收,因此无法做到所有线程都处于工作状态,需要频繁地进行线程切换。
  • 对于Netty等基于event loop的服务器,往往会设置较少的线程数,每个线程都在执行event loop中的request,假设此时是异步IO,那么request在执行时在遇到异步IO时会使用异步回调的方式来处理,线程会立即回收,继续处理下一个request。而异步IO就绪后,又会去触发回调,重新分配线程进行处理。这样就不会使线程进入等待态,致使OS触发线程调度,增加线程切换的开销。但现实情况是,数据库IO基本都是阻塞IO,即使异步回调,也没有办法避免线程进入等待态。
    最好的处理办法就是异步回调+异步IO,而这一点Netty可以实现,协程也可以实现,主要区别只是编程范式,前者需要编写回调逻辑,后者则是编写顺序逻辑(killim可以了解一下,是一个Java协程库)。
  • 但是即使是在阻塞IO的情况下,协程也有其优势所在:线程的维护开销是相对较大的,无法同时创建过多的线程,而协程更为轻量,消耗的内存更少,可以同时存在更多的协程;并且协程的切换也比线程切换的代价要小。要注意一点的是,要通过性能测试来确定,对于IO密集型的应用来说,性能瓶颈究竟在哪里,一定要从最痛的点入手,而非盲目地去改造应用程序。
  • 另外,NIO,或者说IO多路复用,一般是指TCP网络编程中,Server可以以NIO的方式来管理Socket,节省掉的是客户端发来的数据就绪的时间,BIO是当前线程在accept之后,当调用read时如果客户端数据尚未就绪,则进入阻塞;而NIO以轮询的方式来检查客户端数据是否就绪(一个线程就可以管理若干个客户端连接),而非直接阻塞,在就绪后才分配线程,进行read。这里的IO是指网络IO,而前面提到的同步或异步IO,只是数据库IO。
    伪代码示例:
    同步IO
public void count() {
  // query and wait
	Long count = jdbcTemplate.queryForObject("select count(*) from user", Long.class);
  // 后续操作
}

异步IO

public void count() {
	asyncJdbcTemplate.queryForObject("select count(*) from user", Long.class)
  .addListener(new DataCallback<Long> {
  		public void handle(Long data) {
      	// 后续逻辑
      }
  })
}

listener的逻辑会放到event loop中执行,待异步IO返回后才会被分配到某个线程执行。

  • 并发编程中保证数据一致性的关键是如何看待状态。状态,包括内存状态,比如Java中的成员变量,以及数据库状态。不论是Tomcat还是Netty,到我们业务逻辑时都是并发执行的。对于内存状态来说,我们要尽量避免使用可变的成员变量,如果一定用,那么必须要去考虑并发问题,要么加锁,要么使用线程安全的集合,要么使用CAS等无锁算法;对于数据库状态来说,我们要考虑某一段操作是否需要是原子执行的,如果需要,那么就需要使用悲观锁(select for update),或者是乐观锁(version,CAS)。