为什么服务器是多线程的
服务器处理的是来自所有客户端的请求,如果用同一个线程处理,那么不同请求的数据、执行状态等都会相互影响,为了避免这种问题,服务器一般都是多线程的。
如图:
每个线程独立处理,有自己的数据,自己的处理状态,当然也有一些全局数据是应用的所有线程所共享的。有些情况需要对线程做一些特殊的处理,比如java中多线程访问的共享变量可以通过ThreadLocal创建多个副本,比如高并发情况下提高并发量多个请求复用同一线程时使用的concurrent包。
多线程的异步
有一些耗时的操作,比如读取文件,调用远程的rpc接口,多线程模型下会同步的阻塞,线程一直被占用没有释放,导致并发量受操作系统可分配线程数的限制,这是多线程模型的缺点。解决思路是加入一个异步的消息队列,在需要阻塞的地方把发消息到消息队列,消息队列的消费者去做耗时的操作,这样,之前的线程就可以释放,从而复用了,这样使得并发量上限得到了提升。
数据库连接的线程池也是这样,涉及到一些批量操作的时候最好合并成一个操作,通过一个线程来阻塞等待,而不是占用很多的线程,这样并发量上限会高一些。
消息队列的引入使得多线程模型的有了一些异步模型的特点,线程是可复用的,受操作系统线程数上限的影响小了一些。
语言级别的异步模型
一些语言级别的异步模型其实底层也是通过线程池来做的,不过是封装在语言的运行环境里,代码层面声明的异步操作会通过动态分配的线程来处理,之后自动的释放线程,就像内置的性能优化一样。因此,异步模型才能做到不需要手动优化就能获得很高的并发量,因为线程相关的性能优化已经内置了,就像vue和react的区别一样。
但是异步也有缺点,因为只有主线程,一旦某一个执行过程报错没有处理,那么整个应用都会挂掉。所以需要细心的进行异常处理。同时可以结合多进程来扩展。
总结
进程是程序的一次执行过程,线程是进程的一个执行分支,涉及到并发的场景线程是必须的,异步的底层实现也是线程。为了提高并发量,线程需要更合理的分配和释放,需要使用线程池,同时在多线程模型中可以结合一个消息队列等来实现异步,从而更好地利用线程资源。而异步模型自带了线程相关的优化,只需要注意处理错误,同时结合多进程扩展就可以了。
异步和多进程是资源使用方式上的优化。当异步和多进程解决不了问题的时候,就需要提高服务器性能或者使用服务器集群的方式了,从量上来扩展。