一篇不错的IO复用、多进程和多线程三种并发编程模型文章

转载自https://blog.csdn.net/wan_hust/article/details/38441455#t1

我们为什么要用多线程/多进程,多路io复用,毫无疑问是为了提高服务器性能和效率,单个进程处理几个连接是没有问题的,但是服务器一般处理的都是成千上万的连接,这么多连接,可想而知一个进程处理效率得多低,这就相当于一个饭店只有一个服务员招待所有客人,这时候我们想的肯定是招人帮忙,这就产生了多进程,多线程(相当于饭店多招的服务员)处理高并发,但是处理高并发要用哪一种模型比较好呢?这时候我们看下这三种模型的不同之处来选择吧。

多路io模型

I/O复用原理:让应用程序可以同时对多个I/O端口进行监控以判断其上的操作是否可以进行,达到时间复用的目的。在书上看到一个例子来解释I/O的原理,我觉得很形象,如果用监控来自10根不同地方的水管(I/O端口)是否有水流到达(即是否可读),那么需要10个人(即10个线程或10处代码)来做这件事。如果利用某种技术(比如摄像头)把这10根水管的状态情况统一传达到某一点,那么就只需要1个人在那个点进行监控就行了,而类似与select或epoll这样的多路I/O复用机制就好比是摄像头的功能,它们能够把多个I/O端口的状况反馈到同一处,比如某个特定的文件描述符上,这样应用程序只需利用对应的select()或epoll_wait()系统调用阻塞关注这一处即可,这就说明多路io模型比多线程,多进程要好很多,监控的工作不需要创建多个线程、进程去处理,只需要一个select就搞定了

I/O多路复用的优劣:由于I/O多路复用是在单一进程的上下文中的,因此每个逻辑流程都能访问该进程的全部地址空间,所以开销比多进程低得多;缺点是编程复杂度高。

想象一下一个场景,一个服务器同时有几百万个客户端同时连接,如果单用select的话,虽然有select帮忙监控这么多连接,但是单个进程要处理这么多连接还是不行呀,这就需要多线程,多进程帮忙了,所以处理高并发量的最好是用多路io复用+多线程/多进程这种模型



多进程模型

构造并发最简单的就是使用进程,像fork函数。例如,一个并发服务器,在父进程中接受客户端连接请求,然后创建一个新的子进程来为每个新客户端提供服务。
多进程优点:
每个进程互相独立,不影响主程序的稳定性,子进程崩溃没关系;
通过增加CPU,就可以容易扩充性能;
可以尽量减少线程加锁/解锁的影响,极大提高性能,就算是线程运行的模块算法效率低也没关系;
每个子进程都有2GB地址空间和相关资源,总体能够达到的性能上限非常大
多进程缺点:
逻辑控制复杂,需要和主程序交互; 
需要跨进程边界,如果有大数据量传送,就不太好,适合小数据量传送、密集运算 
多进程调度开销比较大;

多线程模型

每个线程都有自己的线程上下文,包括一个线程ID、栈、栈指针、程序计数器、通用目的寄存器和条件码。所有的运行在一个进程里的线程共享该进程的整个虚拟地址空间。由于线程运行在单一进程中,因此共享这个进程虚拟地址空间的整个内容,包括它的代码、数据、堆、共享库和打开的文件。

线程执行的模型:线程和进程的执行模型有些相似,每个进程的声明周期都是一个线程,我们称之为主线程。线程是对等的,主线程跟其他线程的区别就是它先执行。
多线程的优点
无需跨进程边界; 
程序逻辑和控制方式简单; 
所有线程可以直接共享内存和变量等; 
线程方式消耗的总资源比进程方式好; 
多线程缺点
每个线程与主程序共用地址空间,受限于2GB地址空间; 
线程之间的同步和加锁控制比较麻烦; 
一个线程的崩溃可能影响到整个程序的稳定性; 
到达一定的线程数程度后,即使再增加CPU也无法提高性能,例如Windows Server 2003,大约是1500个左右的线程数就快到极限了(线程堆栈设定为1M),如果设定线程堆栈为2M,还达不到1500个线程总数; 
线程能够提高的总性能有限,而且线程多了之后,线程本身的调度也是一个麻烦事儿,需要消耗较多的CPU 


Linux的线程实现是在核外进行的,核内提供的是创建进程的接口do_fork()。内核提供了两个系统调用__clone()和fork(),最终都用不同的参数调用do_fork()核内API。 do_fork() 提供了很多参数,包括CLONE_VM(共享内存空间)、CLONE_FS(共享文件系统信息)、CLONE_FILES(共享文件描述符表)、CLONE_SIGHAND(共享信号句柄表)和CLONE_PID(共享进程ID,仅对核内进程,即0号进程有效)。当使用fork系统调用产生多进程时,内核调用do_fork()不使用任何共享属性,进程拥有独立的运行环境。当使用pthread_create()来创建线程时,则最终设置了所有这些属性来调用__clone(),而这些参数又全部传给核内的do_fork(),从而创建的”进程”拥有共享的运行环境,只有栈是独立的,由 __clone()传入。
         即: Linux下不管是多线程编程还是多进程编程,最终都是用do_fork实现的多进程编程,只是进程创建时的参数不同,从而导致有不同的共享环境。Linux线程在核内是以轻量级进程的形式存在的,拥有独立的进程表项,而所有的创建、同步、删除等操作都在核外pthread库中进行。pthread 库使用一个管理线程(__pthread_manager() ,每个进程独立且唯一)来管理线程的创建和终止,为线程分配线程ID,发送线程相关的信号,而主线程pthread_create()) 的调用者则通过管道将请求信息传给管理线程。



猜你喜欢

转载自blog.csdn.net/qq_35426012/article/details/80030046