操作系统与系统编程——并发(5)

目录

多进程:

多线程:

高并发的问题

进程池与线程池

线程池

五种I/O模型

阻塞式I/O

非阻塞式I/O

信号驱动IO

异步Asynchronous I/O

I/O复用select poll epoll


 

多进程:

 

Server端

 

Client客户端:

关于listen和accept

 

多线程:

多线程需要考虑的问题:

服务器端

客户端同多进程

高并发的问题

 

高并发,服务器受不了,代理服务器和所有的客户端连接,代理服务器将客户端的请求传送给各个服务器去处理,业务量特别大的时候则扩展多个服务器。

代理服务器也可能受不了,也可能瘫痪。

TCP  IP地址+端口号+协议 创建连接。当服务器IP地址更改的时候,则客户端如何连接。

域名转换IP,(DNS 好的域名很贵),只需要到域名服务商将域名对应的IP进行更换即可。客户端通过域名进行连接。

 

进程池与线程池

进程池和线程池概述:

进程池是由服务器预先创建的一组子进程,这些子进程的数目在 3~10 个之间(当然这只是典型情况)。线程池中的线程数量应该和 CPU 数量差不多。

进程池中的所有子进程都比较干净(在服务器启动之初就已经创建好了)运行着相同的代码,并具有相同的属性,比如优先级、 PGID 等。

当有新的任务来到时,主进程将通过某种方式选择进程池中的某一个子进程来为之服务。相比于动态创建子进程,选择一个已经存在的子进程的代价显得小得多。

 

至于主进程选择哪个子进程来为新任务服务,则有两种方法:

(1)主进程使用某种算法来主动选择子进程。

最简单、最常用的算法是随机算法和 Round Robin (轮流算法)。

(2)主进程和所有子进程通过一个共享的工作队列来同步,子进程都睡眠在该工作队列上。当有新的任务到来时,主进程将任务添加到工作队列中。这将唤醒正在等待任务的子进程,不过只有一个子进程将获得新任务的“接管权”,它可以从工作队列中取出任务并执行之,而其他子进程将继续睡眠在工作队列上。

当选择好子进程后,主进程还需要使用某种通知机制来告诉目标子进程有新任务需要处理,并传递必要的数据。最简单的方式是,在父进程和子进程之间预先建立好一条管道,然后通过管道来实现所有的进程间通信。在父线程和子线程之间传递数据就要简单得多,因为我们可以把这些数据定义为全局,那么它们本身就是被所有线程共享的

处理多客户连接:

若客户端连接是长连接,即多次请求可以用一个连接,则可以用进程池中一个进程为其服务,但是若是无状态连接,则需要多个进程为其服务。

 

线程池

为什么需要线程池

大多数的网络服务器,包括Web服务器都具有一个特点,就是单位时间内必须处理数目巨大的连接请求,但是处理时间却是比较短的。在传统的多线程服务器模型中是这样实现的:一旦有个请求到达,就创建一个新的线程,由该线程执行任务,任务执行完毕之后,线程就退出。这就是”即时创建,即时销毁的策略。尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务是执行时间较短,而且执行次数非常频繁(短链接),那么服务器就将处于一个不停的创建线程和销毁线程的状态。这笔开销是不可忽略的,尤其是线程执行的时间非常非常短的情况。

线程池原理

应用程序启动之后,马上创建一定数量的线程,放入空闲的队列中。这些线程都是处于阻塞状态,这些线程只占一点内存,不占用CPU。当任务到来后,线程池将选择一个空闲的线程,将任务传入此线程中运行。当所有的线程都处在处理任务的时候,线程池将自动创建一定数量的新线程,用于处理更多的任务。执行任务完成之后线程并不退出,而是继续在线程池中等待下一次任务。当大部分线程处于阻塞状态时,线程池将自动销毁一部分的线程,回收系统资源。

线程池任务分配方式:提前创造多个线程,处于阻塞状态

  1. 每个线程都阻塞于accept上,当有客户连接时,阻塞与accept的用户都被唤醒,竞争,只有一个去处理accept,其他线程继续阻塞。(惊群效应:只有一个接入,唤醒了所有线程竞争,对资源浪费)
  2. 设置一个主控线程,主控线程accept,其他线程等待主控线程分配任务,accept接收到的连接保存在一个数组里,从线程池里面按照队列取线程分配数组里面的套接字描述符进行任务分配。
  3. 每一个线程争抢一把锁,谁抢到mutex谁去accept,accept完立即解锁。

线程池的作用

需要大量线程来完成任务,且完成任务的时间比较短;对性能要求苛刻的应用;

线程池局限性:

内存池的原理

在软件开发中,有些对象使用非常频繁,那么我们可以预先在堆中实例化一些对象,我们把维护这些对象的结构叫“内存池”。在需要用的时候直接从内存池中拿,而不用从新实例化,在要销毁的时候,不是直接free/delete,而是返还给内存池。把那些常用的对象存在内存池中,就不用频繁的分配/回收内存,可以相对减少内存碎片,更重要的是实例化这样的对象更快,回收也更快。当内存池中的对象不够用的时候就扩容。内存池对象不是线程安全的,在多线程编程中,创建一个对象时必须加锁。

 

五种I/O模型

所谓的IO模型,描述的是出现I/O等待时进程的状态以及处理数据的方式。围绕着进程的状态、数据准备到kernel buffer再到app buffer的两个阶段展开。其中数据复制到kernel buffer的过程称为数据准备阶段,数据从kernel buffer复制到app buffer的过程称为数据复制阶段。

五种网络I/O:阻塞式I/O,非阻塞式I/O,信号驱动I/O,多路I/O复用模型,异步I/O;

阻塞式I/O

 

非阻塞式I/O

Linux下可以通过设置socket设置非阻塞I/O。fcntl(fd,F_SETFL,O_NONBLOCK)

信号驱动IO

当开启了信号驱动功能时,首先发起一个信号处理的系统调用,如sigaction(),这个系统调用会立即返回。但数据在准备好时,会发送SIGIO信号,进程收到这个信号就知道数据准备好了,于是发起操作数据的系统调用,如read()。

在发起信号处理的系统调用后,进程不会被阻塞,但是在read()将数据从kernel buffer复制到app buffer时,进程是被阻塞的。如图:

异步Asynchronous I/O

当设置为异步IO模型时,httpd首先发起异步系统调用(如aio_read(),aio_write()等),并立即返回。这个异步系统调用告诉内核,不仅要准备好数据,还要把数据复制到app buffer中。

httpd从返回开始,直到数据复制到app buffer结束都不会被阻塞。当数据复制到app buffer结束,将发送一个信号通知httpd进程。

看上去异步好,但是,在复制kernel buffer数据到app buffer中时是需要CPU参与的,这意味着不受阻的httpd会和异步调用函数争用CPU。如果并发量比较大,httpd接入的连接数可能就越多,CPU争用情况就越严重,异步函数返回成功信号的速度就越慢。如果不能很好地处理这个问题,异步IO模型也不一定就好。

阻塞、非阻塞、IO复用、信号驱动都是同步IO模型。因为在发起操作数据的系统调用(如本文的read())过程中是被阻塞的。这里要注意,虽然在加载数据到kernel buffer的数据准备过程中可能阻塞、可能不阻塞,但kernel buffer才是read()函数的操作对象,同步的意思是让kernel buffer和app buffer数据同步。显然,在保持kernel buffer和app buffer同步的过程中,进程必须被阻塞,否则read()就变成异步的read()。

只有异步IO模型才是异步的,因为发起的异步类的系统调用(如aio_read())已经不管kernel buffer何时准备好数据了,就像后台一样,aio_read()可以一直等待kernel buffer中的数据,在准备好了之后,aio_read()自然就可以将其复制到app buffer。

I/O复用select poll epoll

IO复用是Linux中的IO模型之一,IO复用就是进程预先告诉内核需要监视的IO条件,使得内核一旦发现进程指定的一个或多个IO条件就绪,就通过进程进行处理,从而不会在单个IO上阻塞了。Linux中,提供了select、poll、epoll三种接口函数来实现IO复用。

 

一个周期:

优点:

缺点:

  select需要探测的句柄很多时,需要很多时间轮询句柄,效率低。epoll更高效但是各个平台差异大。

  将时间响应和事件探测夹在一起,如果event1的响应函数很复杂,执行很慢,event2的响应函数迟迟得不到响应,且影响其他事件检测。

可以用更高效的事件驱动库来解决:libev、libevent库等支持异步响应。

 

 

select

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

fd_set结构体是文件描述符集,该结构体实际上是一个整型数组,数组中的每个元素的每一位标记一个文件描述符。fd_set能容纳的文件描述符数量由FD_SETSIZE指定,一般情况下,FD_SETSIZE等于1024,这就限制了select能同时处理的文件描述符的总量。

  1)nfds参数指定被监听的文件描述符的总数。通常被设置为select监听的所有文件描述符中最大值加1;

  2)readfds、writefds、exceptfds分别指向可读、可写和异常等事件对应的文件描述符集合。这三个参数都是传入传出型参数,指的是在调用select之前,用户把关心的可读、可写、或异常的文件描述符通过FD_SET函数分别添加进readfds、writefds、 exceptfds文件描述符集,select将对这些文件描述符集中的文件描述符进行监听,如果有就绪文件描述符,select会重置readfds、writefds、exceptfds文件描述符集来通知应用程序哪些文件描述符就绪。这个特性将导致select函数返回后,再次调用select之前,必须重置我们关心的文件描述符,也就是三个文件描述符集已经不是我们之前传入的了。

   3)timeout参数用来指定select函数的超时时间。

struct timeval

{

    long tv_sec;        //秒数

    long tv_usec;       //微秒数

};

函数(宏实现)用来操纵文件描述符集:

void FD_SET(int fd, fd_set *set);   //在set中设置文件描述符fd

void FD_CLR(int fd, fd_set *set);   //清除set中的fd

int  FD_ISSET(int fd, fd_set *set); //判断set中是否设置了文件描述符fd

void FD_ZERO(fd_set *set);//清空set中的所有位(在使用文件描述符集前,应该先清空一下)

select的返回情况:

1)如果指定timeout为NULL,select会永远等待下去,直到有一个文件描述符就绪,select返回;

2)如果timeout的指定时间为0,select根本不等待,立即返回;

3)如果指定一段固定时间,则在这一段时间内,如果有指定的文件描述符就绪,select函数返回,如果超过指定时间,select同样返回。

4)返回值情况:

a)超时时间内,如果文件描述符就绪,select返回就绪的文件描述符总数(包括可读、可写和异常),如果没有文件描述符就绪,select返回0;

b)select调用失败时,返回 -1并设置errno,如果收到信号,select返回 -1并设置errno为EINTR。

select的缺点:

1、单个进程能够监视的文件描述符的数量存在最大限制,通常是1024。由于select采用轮询的方式扫描文件描述符,文件描述符数量越多,性能越差;

2、每次调⽤用select,都需要把fd集合从用户态拷贝到内核态,这个开销大

3、Select返回的是含有整个句柄的数组,应用程序需要遍历整个数组才能发现哪些句柄发生事件;

4、Select的触发方式是水平触发,应用程序如果没有完成对一个已经就绪的文件描述符进行IO操作,那么每次select调用还会将这些文件描述符通知进程。

 

   

构造一个cilent数组表示记录链接confd;

1,FD_SET构造监控的文件描述符集。

2,for循环中select()监控文件描述符集;

3,select返回,FD_ISSET判断监控的文件描述符集是否listen。

4,是listen则accept,然后调用FD_SET将其加入监控文件描述符集。Cilent记录新的confd。判断select返回的nready-1是否为0,不为零继续阻塞监听。

5,非4,则检测是否是socked是则遍历cilent检测那个有数据到达。则进行socked操作。

Poll

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

参数解释:(1)fds:指向一个结构体数组的第0个元素的指针,每个数组元素都是一个struct pollfd结构,用于指定测试某个给定的fd的条件 (2)nfds:表示fds结构体数组的长度 (3)timeout:表示poll函数的超时时间,单位是毫秒

函数功能: 监视并等待多个文件描述符的属性变化

函数返回值: (1)返回值小于0,表示出错 (2)返回值等于0,表示poll函数等待超时 (3)返回值大于0,表示poll由于监听的文件描述符就绪返回,并且返回结果就是就绪的文件描述符的个数。

不同与select使用三个位图来表示三个fdset的方式,poll使用一个 pollfd的指针实现。

struct pollfd {

    int fd; /* file descriptor */

    short events; /* requested events to watch */

    short revents; /* returned events witnessed */

};

pollfd结构包含了要监视的event和发生的event,不再使用select“参数-值”传递的方式。同时,pollfd并没有最大数量限制(但是数量过大后性能也是会下降)。 和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符。

优点:

(1)poll() 不要求开发者计算最大文件描述符加一的大小。

(3)它没有最大连接数的限制,原因是它是基于链表来存储的。

(4)在调用函数时,只需要对参数进行一次设置就好了

缺点:

(1)大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。

(2)与select一样,poll返回后,需要轮询pollfd来获取就绪的描述符,这样会使性能下降

(3)同时连接的大量客户端在一时刻可能只有很少的就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降

 

与select相比,poll使用链表保存文件描述符,没有了监视文件数量的限制(几十万),但其他三个缺点依然存在

epoll

epoll对象内部用红黑树保存事件进行插入删除等,用双向链表保存发生的事件

首先通过epoll_create()系统调用在内核中创建一个eventpoll类型的句柄,其中包括红黑树根节点和双向链表头节点。而所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当相应的事件发生时会调用这个回调方法。这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中。

然后通过epoll_ctl()系统调用,向epoll对象的红黑树结构中添加、删除、修改感兴趣的事件,返回0标识成功,返回-1表示失败。

最后通过epoll_wait()系统调用判断双向链rdlist表是否为空,如果为空则阻塞。当文件描述符状态改变,fd上的回调函数被调用,该函数将fd加入到双向链表中,此时epoll_wait函数被唤醒,返回就绪好的事件

 

epoll  LT  ET 工作模式的理解:

epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger),LT是默认模式。

LT模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件

ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。ET模式很大程度上减少了epoll事件的触发次数,因此效率比LT模式下高。

上面所说的select缺点在epoll上不复存在,epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。epoll是事件触发的,不是轮询查询的。没有最大的并发连接限制,内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递

 

 

 

区别总结:

 

  1. 支持一个进程所能打开的最大连接数
  • Select最大1024个连接,最大连接数有FD_SETSIZE宏定义,其大小是32位整数表示,可以改变宏定义进行修改,可以重新编译内核,性能可能会影响;
  • Poll没有最大连接限制,原因是它是基于链表来存储的
  • 连接数限数有上限,但是很大;
  1. FD剧增后带来的IO效率问题
  • 因为每次进行线性遍历,所以随着FD的增加会造成遍历速度下降,效率降低;
  • Poll同上;
  • 因为epool内核中实现是根据每个fd上的callback函数来实现的只有活跃的socket才会主动调用callback,所以在活跃socket较少的情况下,使用epoll没有前面两者的现象下降的性能问题。
  1. 消息传递方式
  • select内核需要将消息传递用户空间都需要内核拷贝
  • poll同上;
  • epoll通过内核和用户空间共享来实现的。

参考:

APUE
UNP
<<Linux高性能服务器编程>>

https://www.jianshu.com/p/397449cadc9a

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值