当在读这篇文章的时候,你想过没有,服务器是怎么把这篇文章发送给你的呢?程序员
说简单也简单,不就是一个用户请求吗?服务器根据请求从数据库中捞出这篇文章,而后经过网络发回去。web
说复杂也复杂,服务器是如何并行处理成千上万个用户请求呢?这里面涉及到哪些技术呢?数据库
这篇文章就来为你解答这个问题。编程
历史上最先出现也是最简单的一种并到处理多个请求的方法就是利用多进程。服务器
好比在Linux世界中,咱们可使用fork、exec等方法建立多个进程,咱们能够在父进程中接收用户的连接请求,而后建立子进程去处理用户请求,就像这样:网络
这种方法的优势就在于:多线程
多进程并行处理的优势和明显,可是缺点一样明显:并发
幸亏,除了进程,咱们还有线程。异步
不是建立进程开销大吗?不是进程间通讯困难吗?这些对于线程来讲通通不是问题。socket
什么?你还不了解线程,赶忙看看这篇《看完这篇还不懂高并发中的线程与线程池你来打我(内含20张图)》,这里详细讲解了线程这个概念是怎么来的。
因为线程共享进程地址空间,所以线程间通讯自然不须要借助任何通讯机制,直接读取内存就行了。
线程建立销毁的开销也变小了,要知道线程就像寄居蟹同样,房子(地址空间)都是进程的,本身只是一个租客,所以很是的轻量级,建立销毁的开销也很是小。
咱们能够为每一个请求建立一个线程,即便一个线程因执行I/O操做——好比读取数据库等——被阻塞暂停运行也不会影响到其它线程,就像这样:
但线程就是完美的、包治百病的吗,显然,计算机世界历来没有那么简单。
因为线程共享进程地址空间,这在为线程间通讯带来便利的同时也带来了无尽的麻烦。
正是因为线程间共享地址空间,所以一个线程崩溃会致使整个进程崩溃退出,同时线程间通讯简直太简单了,简单到线程间通讯只须要直接读取内存就能够了,也简单到出现问题也极其容易,死锁、线程间的同步互斥、等等,这些极容易产生bug,无数程序员宝贵的时间就有至关一部分用来解决多线程带来的无尽问题。
虽然线程也有缺点,可是相比多进程来讲,线程更有优点,但想单纯的利用多线程就能解决高并发问题也是不切实际的。
由于虽然线程建立开销相比进程小,但依然也是有开销的,对于动辄数万数十万的连接的高并发服务器来讲,建立数万个线程会有性能问题,这包括内存占用、线程间切换,也就是调度的开销。
所以,咱们须要进一步思考。
到目前为止,咱们提到“并行”二字就会想到进程、线程。可是,并行编程只能依赖这两项技术吗,并非这样的。
还有另外一项并行技术普遍应用在GUI编程以及服务器编程中,这就是近几年很是流行的事件驱动编程,event-based concurrency。
你们不要以为这是一项很难懂的技术,实际上事件驱动编程原理上很是简单。
这一技术须要两种原料:
剩下的就简单了:
你只须要安静的等待event到来就好,当event到来以后,检查一下event的类型,并根据该类型找到对应的event处理函数,也就是event handler,而后直接调用该event handler就行了。
That's it !
以上就是事件驱动编程的所有内容,是否是很简单!
从上面的讨论能够看到,咱们须要不断的接收event而后处理event,所以咱们须要一个循环(用while或者for循环均可以),这个循环被称为Event loop。
使用伪代码表示就是这样:
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操做完成就能够继续处理接下来的用户请求。
如今你应该明白了吧,虽然上一个用户请求尚未处理完咱们其实就能够处理下一个用户请求了,这就是并行,这种并行就能够用事件驱动编程来处理。
这就比如餐厅服务员同样,一个服务员不可能一直等这上一个顾客下单、上菜、吃饭、买单以后才接待下一个顾客,服务员是怎么作的呢?当一个顾客下完单后直接处理下一个顾客,当顾客吃完饭后会本身回来买单结帐的。
看到了吧,一样是一个服务员也能够同时处理多个顾客,这个服务员就至关于这里的Event loop,即便这个event loop只运行在一个线程(进程)中也能够同时处理多个用户请求。
相信你已经对事件驱动编程有一个清晰的认知了,那么接下来的问题就是事件驱动、事件驱动,那么这个事件也就是event该怎么获取呢?
从《终于明白了,一文完全理解I/O多路复用》这篇文章中咱们知道,在Linux/Unix世界中一切皆文件,而咱们的程序都是经过文件描述符来进行I/O操做的,固然对于socket也不例外,那咱们该如何同时处理多个文件描述符呢?
IO多路复用技术正是用来解决这一问题的,经过IO多路复用技术,咱们一次能够监控多个文件描述,当某个文件(socket)可读或者可写的时候咱们就能获得通知啦。
这样IO多路复用技术就成了event loop的发动机,源源不断的给咱们提供各类event,这样关于event来源就解决了。
固然关于IO多路复用技术的详细讲解请参见《终于明白了,一文完全理解I/O多路复用》。
至此,关于利用事件驱动来实现并发编程的全部问题都解决了吗?event的来源问题解决了,当获得event后调用相应的handler,看上去大功告成了。
想想还有没有其它问题?
如今,咱们可使用一个线程(进程)就能基于事件驱动进行并行编程,再也没有了多线程中让人恼火的各类锁、同步互斥、死锁等问题了。
可是,计算机科学中历来没有出现过一种能解决全部问题的技术,如今没有,在可预期的未来也不会有。
那上述方法有什么问题吗?
不要忘了,咱们event loop是运行在一个线程(进程),这虽然解决了多线程问题,可是若是在处理某个event时须要进行IO操做会怎么样呢?
在《读取文件时,程序经历了什么》一文中,咱们讲解了最经常使用的文件读取在底层是如何实现的,程序员最经常使用的这种IO方式被称为阻塞式IO,也就是说,当咱们进行IO操做,好比读取文件时,若是文件没有读取完成,那么咱们的程序(线程)会被阻塞而暂停执行,这在多线程中不是问题,由于操做系统还能够调度其它线程。
可是在单线程的event loop中是有问题的,缘由就在于当咱们在event loop中执行阻塞式IO操做时整个线程(event loop)会被暂停运行,这时操做系统将没有其它线程能够调用,由于系统中只有一个event loop在处理用户请求,这样当event loop线程被阻塞暂停运行时全部用户请求都没有办法被处理,你能想象当服务器在处理其它用户请求读取数据库致使你的请求被暂停吗?
所以,在基于事件驱动编程时有一条注意事项,那就是不容许发起阻塞式IO。
有的同窗可能会问,若是不能发起阻塞式IO的话,那么该怎样进行IO操做呢?
有阻塞式IO,就有非阻塞式IO。
为克服阻塞式IO所带来的问题,现代操做系统开始提供一种新的发起IO请求的方法,这种方法就是异步IO,对于的,阻塞式IO就是同步IO,关于同步和异步这两个概念关注公众号“码农的荒岛求生”并回复数字“5”你就知道答案了。
异步IO时,假设调用aio_read函数(具体的异步IO API请参考具体的操做系统平台),也就是异步读取,当咱们调用该函数后能够当即返回,并继续其它事情,虽然此时该文件可能尚未被读取,这样就不会阻塞调用线程了。此外,操做系统还会提供其它方法供调用线程来检测IO操做是否完成。
就这样,在操做系统的帮助下IO的阻塞调用问题也解决了。
虽然有异步IO来解决event loop可能被阻塞的问题,可是基于事件编程依然是困难的。
首先,咱们提到,event loop是运行在一个线程中的,显然一个线程是没有办法充分利用多核资源的,有的同窗可能会说那就建立多个event loop实例不就能够了,这样就有多个event loop线程了,可是这样一来多线程问题又会出现。
另外一点在于编程方面,在《从小白到高手,你须要理解同步与异步》这篇文章中咱们讲到过,异步编程须要结合回调函数,这种编程方式须要把处理逻辑分为两部分,一部分调用方本身处理,另外一部分在回调函数中处理,这一编程方式的改变加剧了程序员在理解上的负担,基于事件编程的项目后期会很难扩展以及维护。
那么有没有更好的方法呢?
要找到更好的方法,咱们须要解决问题的本质,那么这个本质问题是什么呢?
为何咱们要使用异步这种难以理解的方式编程呢?
是由于阻塞式编程虽然容易理解但会致使线程被阻塞而暂停运行。
那么聪明的你必定会问了,有没有一种方法既能结合同步IO的简单理解又不会因同步调用致使线程被阻塞呢?
答案是确定的,关注公众号“码农的荒岛求生”,并回复“高性能”你就知道答案啦。
虽然基于事件编程有这样那样的缺点,可是在当今的高性能高并发服务器上基于事件编程方式依然很是流行,但已经不是纯粹的基于单一线程的事件驱动了,而是event loop + multi thread + user level thread。
关于这一组合,一样值得拿出一篇文章来说解,咱们将在后续文章中详细讨论。
高并发技术从最开始的多进程一路演进到当前的事件驱动,计算机技术就像生物同样也在不断演变进化,但无论怎样,了解历史才能更深入的理解当下。但愿这篇文章能对你们理解高并发服务器有所帮助。