进程(线程)并发编程模型(进程模型和网络模型)

一、进程模型

1、单进程单线程

        适用于CPU密集型,CPU一直处于满负荷状态。无I/O操作,无需进程切入或者切出,无需进程切换。

2、多进程

        适用于I/O密集型任务,是指磁盘I/O、网络I/O占主要的任务,计算量很小。比如请求网页、读写文件等。提高CPU使用率。

3、进程池

        适用于I/O密集型任务,首先I/O密集型操作产生等待,进程池可以预先创建一批进程,并有自己的策略(轮询),节省调用API创建资源(销毁)所消耗CPU的时间。其次,预先创建,能够以空间换时间,提高性能。

二、多进程与多线程的区别?

1、健壮性;

2、线程在进程内部可以共享资源,对线程同步时,对线程资源的开销小;

3、多线程产生上下文切换快,因为其维护的资源少。

三、池大小的选择?

        对于CPU密集任务而言,其尽量使用较小的线程池,一般为CPU核心数+1。因为CPU密集型任务使得CPU使用率很高,若开过多的线程数,只能增加上下文切换的次数,因此会带来额外的开销。

        对于I/O密集性任务而言,可以使用稍大的线程池,一般为N*CPU核心数。 IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候去处理别的任务,充分利用CPU时间。

四、网络模型

1、阻塞,非阻塞、I/O复用

  •  阻塞:阻塞调用是指调用结果返回之前,当前线程会被挂起(线程进入非可执行状态,在这个状态下,cpu不会给线程分配时间片,即线程暂停运行)。函数只有在得到结果之后才会返回。

        缺点:对于客户端而言,connect(),accept(),read(),write()等阻塞函数使得客户端串行化,当客户端发起连接并建立连接时,由于accept()是阻塞的,因此当第二个连接连接时,由于服务器还在处理第一个连接的内容,因此无法被accept(新的连接已经由connect触发三次握手,处于established的状态)。

         解决方法:通过多进程/线程做。accept不断接受连接(通过调用fork来创建更多进程)

         缺点:①进程(线程)的创建需要消耗服务器的资源;②会达到服务器资源上限;③上下文切换次数增多;④响应时间不够及时;⑤进程(线程)使用率低,原因在于客户端可能不会发数据,且服务端不会主动断开连接。

  • 非阻塞:可以让一个进程接受多个客户端,提高进程的利用率,减轻系统开销,提高系统的吞吐率。

        缺点:①延迟响应;②轮询导致CPU利用率低,增大同一I/O切入内核的次数,增加同一I/O切入内核的次数(数据的读取)

  •  I/O复用(复用的是进程或线程):有一个代理函数,将感兴趣的fd写入,利用该代理函数批量询问内核,并将函数设置为非阻塞,因为该代理函数返回的就是就绪事件、使得CPU利用率很高,相应延迟降低。

 int select(int maxfdp, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout);

返回值:负值(select错误)    正值:就绪事件个数   0:等待超时,没有可读写或错误文件。

maxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!
readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。
writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。
errorfds同上面两个参数的意图,用来监视文件是否发生错误异常。
timeout 是select的超时时间,这个参数至关重要。它可以使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构,就是将select置于永久阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来返回正值,超时返回0。                  

        缺点:①select处理fd个数,上限为1024(只有通过重新编译内核方可改动);

                   ②调用select之前都需重新注册文件描述符以及关心的读写事件,而且需要将事件从用户态拷贝到内核态(使用从copy_from_user从用户空间拷贝fd_set到内核空间。)。

                  ③select返回就绪fd之后,仍需一次遍历fd哪类事件是就绪的。第一类是I/O是监听I/O,第二类是读写I/O,此时需调用accept解决,并将其设置为非阻塞。

                   ④ 当select插入内核中,需要遍历每个fd,若就绪,则将其修改为就绪,内核需要将所有I/O事件都轮询一遍,两次轮询的效率非常低,消耗CPU时间,CPU使用率低,用户处理延时增大。

2、poll、epoll

  • poll

 int poll (struct pollfd *fds, unsigned int nfds, int timeout);

               返回:若有就绪描述符则为其数目。若超时则为0,若出错则为-1。

struct pollfd *fds                                  

第一个参数是指向一个结构数组第一个元素的指针。每个数组元素都是一个pollfd结构,用于指定测试某个给定描述符fd的条件。

    events成员告诉poll监听fd上的哪些事件,它是一系列事件的按位或;revents成员则由内核修改,以通知应用程序fd上实际发生了哪些事件。

unsigned int nfds                         指定被监听事件集合fds的大小。

        优缺点:避免了select上限的问题,拷的是状态,但无法避免两次轮询。

  • epoll
int epoll_create(int size)epoll_create创建内核事件表,即epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中。epoll需要使用一个额外的文件描述符,来唯一标识内核中的这个事件表。该函数返回的文件描述符将用作其他所有epoll系统调用的第一个参数,以指定访问的内核事件表。
int epoll_wait(int epfd,struct epoll_event* events, int maxevents, int timeout)该函数成功时返回就绪的文件描述个数,失败时返回-1并设置errno。该函数如果检测到事件,就将所有就绪事件从内核事件表中负值到它的第二个参数events指向的数组中。这个数组只用于输出epoll_wait检测到的就绪事件。这就极大的提高了应用程序索引到就绪文件描述符的效率。

        优缺点:

             ①1024的解决:epoll_create注册内核事件文件描述表,将感兴趣的fd注册到内核,该表维护了用户注册的每个事件,而这些时间仅在第一次注册时进行了拷贝,从而极大减少了fd拷贝内核态的次数。

              ② 检测到哪些事件就绪过程中采用注册回调机制,放弃轮询机制。将就绪事件放置在就绪链表中,返回就绪链表即可。

总结:

        前面我们讨论了select poll 和epoll三组IO复用系统调用,这3组系统调用都能同时监听多个文件描述符。它们将等待由timeout参数指定的超时时间,直到一一个或者多个文件描述符上有事件发生时返回,返回值是就绪的文件描述符的数量。返回0表示没有事件发生。现在我们从事件集、最大支持文件描述符数、工作模式和具体实现等四个方面进一一步比较它们的异同,以明确在实际应用中应该选择使用哪个(或哪些)。

        这3组函数都通过某种结构体变量来告诉内核监听哪些文件描述符上的哪些事件,并使用该结构体类型的参数来获取内核处理的结果。select 的参数类型fd _set没有将文件描述符和事件绑定,它仅仅是一一个文件描述符集合,因此select需要提供3个这种类型的参数来分别传人和输出可读、可写及异常等事件。这一方面使得select不能处理更多类型的事件,另一方面由于内核对fd set集合的在线修改,应用程序下次调用select前不得不重置这3个fd_set集合。poll 的参数类型pollfd则多少“聪明”一些。它把文件描述符和事件都定义其中,任何事件都被统“ 处理,从而使得编程接口简洁得多。并且内核每次修改的是polfd结构体的revents成员,而events成员保持不变,因此下次调用poll时应用程序无须重置pollfd类型的事件集参数。由于每次select和poll调用都返回整个用户注册的事件集合(其中包括就绪的和未就绪的),所以应用程序索引就绪文件描述符的时间复杂度为0 (n)。epoll 则采用与select和poll完全不同的方式来管理用户注册的事件。它在内核中维护一个事件表,并提供了一个独立的系统调用epoll ctl 来控制往其中添加、删除、修改事件。这样,每次epollwait调用都直接从该内核事件表中取得用户注册的事件,而无须反复从用户空间读人这些事件。epoll wait 系统调用的events参数仅用来返回就绪的事件,这使得应用程序索引就绪文件描述符的时间复杂度达到0 (1)。poll和epoll wait分别用nfds和maxevents参数指定最多监听多少个文件描述符和事件。这两个数值都能达到系统允许打开的最大文件描述符数目,即65 535 Carprolsys/s/flemax)。而sele允许监听的最大文件描述符数量通常有限制。虽然用户可以修改这个限制,但这可能导致不可预期的后果。
 

 

 

 

          

          

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值