在Linux网络编程中,我们应该见过很多网络框架或者server,有多进程的处理方式,也有多线程处理方式,孰好孰坏并没有可比性,首先选择多进程还是多线程我们需要考虑业务场景,其次结合当前部署环境,是云原生还是传统的IDC等,最后考虑可维护性,其具体的对比在第三部分具体会展开说。
上面是一个创建进程的函数,那执行当前函数内核会做哪些事情呢?
(1)如果需要创建进程需要调用,进程调用fork,当控制转移到内核中的fork代码;
(2)内核做分配新的内存块和内核数据给子进程;
(3)内核将父进程部分数据结构内容拷贝进子进程,有一部分使用写时复制(copy on write)和父进程共享;
(4)添加子进程到系统进程列表中,同时父进程打开的文件描述符默认在子进程也会打开,且描述符引用计数加1;
(5)返回,内核调度器开始调度,因此之后,变成两个执行流;
(2)如果父进程不在,此时子进程会被init进程接管,并等待结束,如果此时子进程一直不退出,就会一直占用内核资源;
在多进程编程模式中,各个进程不是孤立的,需要处理进程间通讯(IPC),如果您已经有所了解可以一起温故。
(1)管道
管道通讯方式在前面已经讲过,通过系统函数创建fd[0]和fd[1],其中两个句柄就可以提供给父进程和子进程写入或者读出数据。
(2)信号量
信号量是为了解决访问临界区提供的一种特殊变量,支持两种操作:等待和信号,也就是对应P(进入临界区),V(退出临界区);
假设现在有信号量SV,其执行:
- P(SV),如果,SV将减1;如果,挂起的当前进程;
- V(SV),如果有等待SV的进程则唤醒,如果没有则SV将加1;
Linux系统API如下:
创建信号量,操作信号量,对应PV操作,允许对信号量直接控制,为了方便大家理解,在此给一段代码。
(3)共享内存
共享内存是在有些场景下,父进程和子进程需要读写大块的数据,因此Linux系统提供了,,,四个系统调用。
创建共享内存或者获取已存在的共享内存,标识全局唯一共享内存,为设置共享内存大小,设置的一些宏;
共享内存被创建以后,不能直接访问,需要关联到进程的地址空间中,可以设置由操作系统选择;
和调用类似,是POSIX方法,创建一个共享内存对象,返回句柄与mmap调用;
删除共享内存标记;
为了方便大家理解,在此给一段代码:
注意 :共享内存需要考虑多写多读的问题,如果多个进程写,需要加锁处理。
(4)消息队列
创建消息队列,标识全局唯一,和其他IPC的参数类似;
和是发送和写入消息类型的数据;
为了方便大家理解,在此给一段代码:
(5)UNIX域
除了以上的通用的IPC,socket的UNIX域也可以作为进程间通讯,比如使用,或系统调用,或父进程创建一个环回接口socket server,子进程通过socket client访问。
在多进程的网络编程中,实现方式有很多,但是总体还是围绕两条线,其一如何将新建连接分发给子进程,其二如何将数据/信号传给子进程,并监控子进程,下图是其实现方式之一(由于实现细节很多,后续会将实现代码开源到github):
多进程
(1)首先为了性能考虑,进程池是必须的,通过线程池不需要频繁创建和销毁进程;
(2)其次主进程对应的新连接,考虑各个进程之间负载均衡,将新连接通过随机算法分发给子进程;
(3)分发方式可以通过管道,共享内存,消息队列等方式告知子进程,也可以传递数据信息;
(4)子进程收到新连接的句柄,就可以通过内部的监听IO事件,从而完成和;
在Linux中,线程是轻量级进程,运行在内核空间,由内核调度,最开始的线程库是,但是不符合POSIX标准,后来出现了NGPT和NPTL,其采用的线程模型不一样,所以性能有差异,性能由快到慢是:。
其中线程的模型分为三种:
- 多对一(M:1)的用户级线程模型;
- 一对一(1:1)的内核级线程模型:如和;
- 多对多(M:N)的两极线程模型:如NGPT;
现在Linux的2.6内核版本开始,默认使用NPTL线程库(1:1的线程模型),对比有如下优势:
- 内核线程不再是一个进程,因此避免用进程模拟线程导致的语义问题;
- 摒弃了管理线程,终止线程和回收线程等工作由内核完成;
- 一个进程中的线程可以运行在不同的CPU上,可以充分利用多处理器系统;
- 线程的同步由内核完成,隶属于不同的进程的线程之间也可以共享互斥锁,因此可以实现跨进程的线程同步;
(1)创建线程,表示线程ID,表示设置线程属性,另外传递线程处理函数和参数;
(2)线程退出,可以在执行完成以后调用;
(3)是等待线程结束,调用成功返回0,否则返回错误;
(4)异常终止一个线程;
(5)把指定的线程转变为脱离状态,线程有两种属性,一种是joinable,一种是detached,当一个joinable线程终止时,它的线程ID和退出状态将留存到另一个线程对它调用pthread_join,调用前线程的资源不会释放,而脱离detached线程终止时,资源会立刻释放;
(6)获取当前线程ID;
为了方便大家理解,在此给一段代码(使用c++11语法,底层是以上API的封装):
(1)信号量
这里的API和多进程的信号量类似,就不展开详细说了,其中PV操作对应的函数是信号量减1,信号量加1;
(2)互斥锁
互斥锁是线程独占临界区的控制方式,通过以下系统API:
是锁的初始化,为设置锁属性,主要是类型:
- 普通锁,只能在同一个线程加锁解锁,但是加锁不可重入,其他线程不能解锁当前线程的锁,否则会导致死锁或者不可预期效果;
- 纠错锁,主要提供错误检查;
- 嵌套锁,允许同一个线程重入加锁,不过其他线程需要这个锁,当前锁的拥有者需要执行相应次数的解锁,对已经被其他线程加锁的嵌套锁解锁或者对已经解锁的嵌套锁再解锁,都会返回错误;
- 默认锁,多次加锁解锁等行为是未定义;
与成对出现,这里要注意的是对于非嵌套锁,一定要注意死锁场景,另外不要对执行后的锁再执行加锁或者解锁操作;
(3)条件变量
条件变量是一种线程间通讯机制,当某个共享数据达到某个值得时候,唤醒等待该数据的线程继续执行,其API如下:
初始化条件变量,销毁条件变量和释放占用内核资源,广播唤醒所有等待的线程;
唤醒一个等待的线程,至于哪个被唤醒,取决于线程优先级和调度策略;
其中以上两个等待的函数是,可能大家有点奇怪,为啥需要带一个锁呢?这是确保操作的原子性,调用之前需要将加锁,执行时候,首先会把调用线程放入条件变量的等待队列中,然后将解锁,等返回成功后,对继续加锁,后续处理交给各自线程;
与多进程对比,多线程的处理方式相对就简单很多,由于在多线程内部数据是共享的,所以没有繁琐的数据传递,只需要队列就可以完成主线程和子线程之间的数据通信,下图是其实现方式之一(由于实现细节很多,后续会将实现代码开源到github):
多线程
(1)和进程一样,为了性能考虑,线程池是必须的,这样对于IO密集型场景,处理线程一般是跑不满的;
(2)主线程对应的新连接,将新连接插入,同时通过信号量或条件变量或互斥锁告知线程池中的线程;
(3)线程池的线程收到通知,先开始抢锁,然后从队列中取出新连接;
(4)子线程拿到新连接的句柄,就可以通过内部的监听IO事件,从而完成和;
在云原生时代之前,多进程和多线程的网络框架的争论已久,每个开发者选择都有自己的考虑,比如多进程代表的web server是Nginx,Apache等,多线程的有Varnish,gRPC,libevent库等等,到底该如何选择网络框架呢?
(1)首先结合最大化利用多个处理器的硬件结构和软件架构,在大多数情况下,选择多线程或多进程处理,又或者两者兼用都能实现,但是这个选择将影响软件的性能、后期的维护、可扩展性、内存等各方面,所以开发网络框架之前一定要综合考虑;
(2)考虑多线程的优缺点:
- 优点:多线程最突出的优点是借助变量、对象等,线程之间可以便捷地共享数据,与主线程进行通信也非常容易;在内核部分方面,运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间;
- 缺点:如果其中一个线程崩溃,整个应用程序将连带崩溃;在调试代码方面,多线程调试非常困难,往往很多意想不到的bug都是多线程操作不当产生,但是看日志又可能看不出来;在内核部分多线程可能导致花费大量时间进行上下文切换,影响性能,比如监听socket后,多个线程同时抢占锁导致频繁切换,同时每个线程与主程序共用地址空间,线程内存受限于进程内存空间;还有一个最大的问题就是写代码过程中,必须要考虑锁的情况,如操作全局变量,临界区数据等等,往往使代码的结构比较复杂;
(3)考虑多进程的优缺点:
- 优点:一个进程崩溃,并不意味着整个应用程序的崩溃,这是多进程开发的一个显著优势(内核空间进程除外);调试方便,可以快速从日志或者gdb跟进当前进程的运行状态;写代码需要考虑的锁更少,比如操作全局变量或者临界区,使得代码的整体结构相对简单;
- 缺点:进程之间的通信或者通知比线程之间复杂,需要使用到IPC各种方式;在内核层面,进程越多对于内核调度会越慢,导致整体性能下降;虽然上面优点里面对于进程崩溃更好容错,但是多个进程运行状态,需要主进程监听或者周边程序监控,使维护功能增多;
以上的考虑是基于云原生时代之前,随着容器化的到来,我们应遵循"每个容器一个应用程序"的原则,原因如下:
- 每个容器中只运行一个应用程序,则水平伸缩将变得十分容易;
- 每个容器中只运行一个应用程序,升级程序时能够将影响范围控制再更小的粒度,极大增加应用程序生命周期管理的灵活性,避免在升级某个服务时中断相同容器中的其他进程;
- 每个容器中只运行一个应用程序,可以更好的利用云原生的工具,比如监控,探测等;
版权声明:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权、违法违规、事实不符,请将相关资料发送至xkadmin@xkablog.com进行投诉反馈,一经查实,立即处理!
转载请注明出处,原文链接:https://www.xkablog.com/bcyy/70719.html