当在读这篇文章的时候,你有没有想过,服务器是怎么把这篇文章发送给你的呢?
说简单也简单,不就是一个用户请求吗?服务器根据请求从数据库中捞出这篇文章,然后通过网络发回去。
说复杂也复杂,服务器是如何并行处理成千上万个用户请求呢?这里面涉及到哪些技术呢?
这篇文章就来为你解答这个问题。
多进程
历史上最早出现也是最简单的一种并行处理多个请求的方法就是利用多进程。 比如在Linux世界中,我们可以使用fork、exec等系统调用创建多个进程,我们可以在父进程中接收用户的连接请求,然后创建子进程去处理用户请求,就像这样:- 编程简单,非常容易理解
- 由于各个进程的地址空间是相互隔离的,因此一个进程崩溃后并不会影响其它进程
- 充分利用多核资源
- 各个进程地址空间相互隔离,这一优点也会变成缺点,那就是进程间要想通信就会变得比较困难,你需要借助进程间通信(IPC,interprocess communications)机制,想一想你现在知道哪些进程间通信机制,然后让你用代码实现呢?显然,进程间通信编程相对复杂,而且性能也是一大问题
- 我们知道创建进程开销是比线程要大的,频繁的创建销毁进程无疑会加重系统负担。
多线程
不是创建进程开销大吗?不是进程间通信困难吗?这些对于线程来说统统不是问题。 什么?你还不了解线程,赶紧看看这篇《看完这篇还不懂线程与线程池你来打我》,这里详细讲解了线程这个概念是怎么来的。 由于线程共享进程地址空间,因此线程间通信天然不需要借助任何通信机制,直接读取内存就好了。 线程创建销毁的开销也变小了,要知道线程就像寄居蟹一样,房子(地址空间)都是进程的,自己只是一个租客,因此非常的轻量级,创建销毁的开销也非常小。![9dd1fb25e715659b6bc75d2f20fcd2b2.png](https://img-blog.csdnimg.cn/img_convert/9dd1fb25e715659b6bc75d2f20fcd2b2.png)
![a16d95ba7919a0afcedf853b33d34237.png](https://img-blog.csdnimg.cn/img_convert/a16d95ba7919a0afcedf853b33d34237.png)
Event Loop:事件驱动
到目前为止,我们提到“并行”二字就会想到进程、线程。但是,并行编程只能依赖这两项技术吗,并不是这样的。 还有另一项并行技术广泛应用在GUI编程以及服务器编程中,这就是近几年非常流行的事件驱动编程,event-based concurrency。 大家不要觉得这是一项很难懂的技术,实际上事件驱动编程原理上非常简单。 这一技术需要两种原料:- event
- 处理event的函数,这一函数通常被称为event handler
![516edbf45027befb15f5adc96fa4a8d4.png](https://img-blog.csdnimg.cn/img_convert/516edbf45027befb15f5adc96fa4a8d4.png)
while(true) { event = getEvent(); handler(event);}
Event loop中要做的事情其实是非常简单的,只需要等待event的带来,然后调用相应的event处理函数即可。
注意,这段代码只需要运行在一个线程或者进程中,只需要这一个event loop就可以同时处理多个用户请求。
有的同学可以依然不明白为什么这样一个event loop可以同时处理多个请求呢?
原因很简单,对于web服务器来说,处理一个用户请求时大部分时间其实都用在了I/O操作上,像数据库读写、文件读写、网络读写等。当一个请求到来,简单处理之后可能就需要查询数据库等I/O操作,我们知道I/O是非常慢的,当发起I/O后我们大可以不用等待该I/O操作完成就可以继续处理接下来的用户请求。
![d8492183d986fd2354d1af462fddf8f7.png](https://img-blog.csdnimg.cn/img_convert/d8492183d986fd2354d1af462fddf8f7.png)
事件来源:IO多路复用
在《终于明白了,一文彻底理解I/O多路复用》这篇文章中我们知道,在Linux/Unix世界中一切皆文件,而我们的程序都是通过文件描述符来进行I/O操作的,当然对于socket也不例外,那我们该如何同时处理多个文件描述符呢? IO多路复用技术正是用来解决这一问题的,通过IO多路复用技术,我们一次可以监控多个文件描述,当某个文件(socket)可读或者可写的时候我们就能得到通知啦。 这样IO多路复用技术就成了event loop的原材料供应商,源源不断的给我们提供各种event,这样关于event来源的问题就解决了。![2370ae2d0b689224da85f42721f9a9cb.png](https://img-blog.csdnimg.cn/img_convert/2370ae2d0b689224da85f42721f9a9cb.png)
问题:阻塞式IO
现在,我们可以使用一个线程(进程)就能基于事件驱动进行并行编程,再也没有了多线程中让人恼火的各种锁、同步互斥、死锁等问题了。 但是,计算机科学中从来没有出现过一种能解决所有问题的技术,现在没有,在可预期的将来也不会有。 那上述方法有什么问题吗? 不要忘了,我们event loop是运行在一个线程(进程),这虽然解决了多线程问题,但是如果在处理某个event时需要进行IO操作会怎么样呢? 在《读取文件时,程序经历了什么》一文中,我们讲解了最常用的文件读取在底层是如何实现的,程序员最常用的这种IO方式被称为阻塞式IO,也就是说,当我们进行IO操作,比如读取文件时,如果文件没有读取完成,那么我们的程序(线程)会被阻塞而暂停执行,这在多线程中不是问题,因为操作系统还可以调度其它线程。 但是在单线程的event loop中是有问题的,原因就在于当我们在event loop中执行阻塞式IO操作时整个线程(event loop)会被暂停运行,这时操作系统将没有其它线程可以调度,因为系统中只有一个event loop在处理用户请求,这样当event loop线程被阻塞暂停运行时所有用户请求都没有办法被处理,你能想象当服务器在处理其它用户请求读取数据库导致你的请求被暂停吗?![4a726e008f299081c8ee13faeade9913.png](https://img-blog.csdnimg.cn/img_convert/4a726e008f299081c8ee13faeade9913.png)
非阻塞IO
为克服阻塞式IO所带来的问题,现代操作系统开始提供一种新的发起IO请求的方法,这种方法就是异步IO,对应的,阻塞式IO就是同步IO,关于同步和异步这两个概念可以参考《从小白到高手,你需要理解同步与异步》。 异步IO时,假设调用aio_read函数(具体的异步IO API请参考具体的操作系统平台),也就是异步读取,当我们调用该函数后可以立即返回,并继续其它事情,虽然此时该文件可能还没有被读取,这样就不会阻塞调用线程了。此外,操作系统还会提供其它方法供调用线程来检测IO操作是否完成。 就这样,在操作系统的帮助下IO的阻塞调用问题也解决了。基于事件编程的难点
虽然有异步IO来解决event loop可能被阻塞的问题,但是基于事件编程依然是困难的。 首先,我们提到,event loop是运行在一个线程中的,显然一个线程是没有办法充分利用多核资源的,有的同学可能会说那就创建多个event loop实例不就可以了,这样就有多个event loop线程了,但是这样一来多线程问题又会出现。 另一点在于编程方面,在《从小白到高手,你需要理解同步与异步》这篇文章中我们讲到过,异步编程需要结合回调函数(关于回调函数请才参考《程序员应如何彻底理解回调函数》),这种编程方式需要把处理逻辑分为两部分,一部分调用方自己处理,另一部分在回调函数中处理,这一编程方式的改变加重了程序员在理解上的负担,基于事件编程的项目后期会很难扩展以及维护。 那么有没有更好的方法呢? 要找到更好的方法,我们需要解决问题的本质,那么这个本质问题是什么呢?更好的方法
为什么我们要使用异步这种难以理解的方式编程呢? 是因为阻塞式编程虽然容易理解但会导致线程被阻塞而暂停运行。 那么聪明的你一定会问了,有没有一种方法既能结合同步IO的简单理解又不会因同步调用导致线程被阻塞呢? 答案是肯定的,这就是用户态线程,user level thread,也就是大名鼎鼎的协程,关于协程值得单独拿出一篇文章来讲解,就在下一篇。 虽然基于事件编程有这样那样的缺点,但是在当今的高性能高并发服务器上基于事件编程方式依然非常流行,但已经不是纯粹的基于单一线程的事件驱动了,而是event loop + multi thread + user level thread。 关于这一组合,同样值得拿出一篇文章来讲解,我们将在后续文章中详细讨论。总结
高并发技术从最开始的多进程一路演进到当前的事件驱动,计算机技术就像生物一样也在不断演变进化,但不管怎样,了解历史才能更深刻的理解当下。希望这篇文章能对大家理解高并发服务器有所帮助。![70d04b81634bb11025895d9adc35f8b6.png](https://img-blog.csdnimg.cn/img_convert/70d04b81634bb11025895d9adc35f8b6.png)
往期精选
看完这篇还不懂线程与线程池你来打我 读取文件时,程序经历了什么? 终于明白了,一文彻底理解I/O多路复用 从小白到高手,你需要理解同步与异步程序员应如何彻底理解回调函数
![e3fd7cc1da36895f104c6be5a825a932.png](https://img-blog.csdnimg.cn/img_convert/e3fd7cc1da36895f104c6be5a825a932.png)