Go语言 几种常见的IO模型用法 和 netpoll与原生GoNet对比

【go基础】16.I/O模型与网络轮询器netpoller_go中的多路io复用模型-CSDN博客

字节开源的netPoll多路复用器源码解析-CSDN博客

一、几种常见的IO模型

1. 阻塞I/O

(1) 解释:

    用户调用如accept、read等系统调用,向内核发起I/O请求后,应用程序会从用户态陷入内核态。

    内核会检查文件描述符fd是否就绪,如果没有数据准备就绪,内核就会挂起(阻塞)当前的线程

    当fd中数据就绪后,操作系统内核会将数据从内核空间拷贝到进程空间,并交回控制权。

(2) 缺点:

    一个goroutine同一时刻只能处理一个流,无法解决多个IO请求问题

2. 非阻塞I/O

(1) 解释:

    执行read和write 等I/O操作会立刻返回,然后应用程序不停轮询。读取时如果没有数据,会返回

EAGAIN或EWOULDBLOCK错误码。

(2) 缺点:

    占用cpu资源,不停做无用的轮询操作

3.  I/O多路复用模型

(1) 解释:

    一个进程/线程处理多个I/O,内核同时监视多个文件描述符,一旦某个描述符就绪,通知程序进行相应的读写操作;如果没有文件句柄就绪,就阻塞应用程序,交出CPU。

(2) 缺点:无,基本符合需求。

4. 三种IO模型对比

目前epoll是linux大规模并发网络应用程序的首选。

大部分高性能的网络服务器使用的I/O复用模型均是epoll,比如nginx、redis、memcached等。

二、几种常见的IO模型 使用方法 

     以下模型的 IO 操作都是 使用阻塞的机制。

1、多线程

(1) 解释:

    每accept到一个客户端后,都启动一个新的线程。存在多个线程。

(2) 优点: 简单。

(3) 缺点:

a. 占用内存资源,每个线程需要2M的栈空间;需要的线程很多,且频繁切换。

b. 长连接需要保持连接状态(心跳/健康检查),进一步占用资源。

2、单线程 + 多路IO复用

(1) 解释:

    一个线程 ,处理多个客户端。采用多路I/O复用机制(epoll) 监控多个fd的IO请求

    多个请求是依次执行的,fd1的请求完成了,才去处理fd2的请求。

(2) 优点: 只需要一个线程,节省资源。

(3) 缺点:同一时间只能处理一个客户端操作,会有排队延迟

3、单线程 + 多路IO复用 + 多线程处理业务

(1) 解释:

    把逻辑任务分离到另外的线程池。

    创建专门用于处理逻辑业务的线程池。

    当有客户端消息到达,主线程完成io读操作后,将后续逻辑业务放入线程池的一个线程中执行,主线程继续监控IO请求。当线程执行完逻辑业务之后,将结果消息返回主线程,主线再发送给客户端。

(2) 优点: 将业务放到线程处理,减轻排队延迟。

(3) 缺点:IO 读写数据时的并发能力依然是1

4、单线程(只监听) + 多路IO复用 + 多线程处理业务和IO读写

(1) 解释:

     在3的基础上,让线程池把 IO读写 和 业务 都处理了。

     主线程只负责用来创建fd,每个线程负责监听多个客户端读写操作,以及后续逻辑业务。

     使用例子: GO语言的 原生net 和 netpoll 采用的是这个模式。

(2) 优点: 

a. 可以监听更多的fd,提升io读写的并发能力

b. 充分利用多核CPU

(3) 缺点:同一个线程内的多个客户端,依然是排队的

三、边缘触发和水平触发的区别

1. 水平触发(LT): 若就绪的事件一次没有处理完,就会将没有处理完的事件继续放回到就绪队列之中,下一次可继续触发事件。

    必须使用非阻塞模式。因为水平触发模式会将文件描述符一直读到返回 errno或EAGAIN,非阻塞模式下才会返回这个。 而阻塞模式下不会返回这个,所以会一直阻塞卡死。

2. 边缘触发(ET):就绪的事件只能处理一次,需要一次过把数据读完。

  若没有处理完,会在下次的其它事件就绪时再进行处理。

  而若以后再也没有就绪的事件,剩余的那部分数据也会丢失。

四、Netpoll与原生GoNet对比

1. goroutine 上下文切换调度 压力

Go Net: 高并发时 切换压力。 因为 GoNet只负责监听事件,不负责buff读写操作。读写操作是在用户的goroutine力执行的,执行时间比较长,需要更多的上下文切换。

NetPoll:高并发时 切换压力 。 因为 NetPoll 会执行读写操作,用户的goroutine无需再进行buff读写,所以执行时间比较短,上下文切换比较少。

2. 内存消耗:

Go Net: 需要内存少。 因为GoNet只有一个事件循环,且不负责buff读写, 所以不需要太多内存。

NetPoll:需要内存多。因为 NetPoll 支持多个事件循环,并且需要负责buff读写,所以需要更多的内存。

3. 触发方式

Go Net:使用 边缘触发 ET。

NetPoll:使用 水平触发 LT。

4. Goroutine数目

Go Net:一个Goroutine下只有一个连接。

NetPoll:一个Goroutine下有多个连接。

5. 是否支持共用buff池,少拷贝一次数据。

Go Net:不支持。

NetPoll:支持。管理一个Buffer池直接交给用户,少拷贝一次。

五、原生网络库GoNet实现

1. 基本架构

        原生网络库 基于epoll et模式开发,基本架构如下图所示:

2. goroutine的使用

        每个 文件描述符 fd 对应一个 goroutine。

3.  IO读写逻辑

        业务方对 conn 发起主动的读写,用户层阻塞,底层使用非阻塞 IO。

        当事件未就绪,将 fd 注册(epoll_ctl)进 epoll fd,通过把 goroutine 设置成等待状态。

        当有就绪事件后,唤醒对应 goroutine 成执行状态,从内核拷贝数据到用户层,然后执行后续逻辑。

        GoNet 负责事件监听,以及负责阻塞和唤醒对应goroutine

        采用的是边缘触发,每次唤醒后,都需要一次读完。

        Read接口非阻塞,读取n字节后就返回,上层可以控制从内核缓冲区去数据的速率。

4. 优劣势

优势: 创建连接后,由调用方决定何时进行读写,使用上相对方便。

劣势:

(1) 因为由调用方决定是否进行读写,假如调用方出问题一直不调用,会导致数据一直存在,占用内存。

(2) 一个连接对应一个goroutine,当连接非常多时,资源调度切换上有很大开销。

(3)不支持用户和内核共享buff,多一次buff拷贝。需要把数据从内核层拷贝到用户层,再拷贝到上层。

六、netpoll 设计思路

1. 基本架构

        原生网络库 基于epoll lt模式 开发,基本架构如下图所示:

2. goroutine的使用

       存在一个 poll对象池,每个poll对象带有一个epoll,和单独对应一个 goroutine。 goroutine数目和 poll对象数目一致,每个poll对象可以监听多个 文件描述符fd

3.  IO读写逻辑

        每个poll对象开启一个goroutine来不断轮询当前epoll上的可读可写等事件        

        每个epoll会关联多个fd,每个fd关联一个 Buffer。

        当从 fd 监听到有可读事件后,会把数据读到 Buffer里。

        不断轮询处理 Buffer 的数据,当发现数据读完整之后,通知后面的处理逻辑的协程池 GoPoll去执行        

        与内核的系统调用交互完全由netpoll控制,用户层对 Conn 的读写都只是在操作共用的Buffer而已。

4. 优劣势

优势: 支持高并发能力更强

(1) 轮询处理Buffer的数据,不存在数据得不到处理的情况。

(2) 一个goroutine对应多个连接,即使连接非常多,资源调度切换上的开销也不大。

(3) 支持用户和内核共享buff,减少一次数据拷贝操作,提升效率。

劣势:需要占用更多的内存。

 

  • 20
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
goroutine 52 [select]: github.com/go-sql-driver/mysql.(*mysqlConn).startWatcher.func1() D:/Program Files (x86)/Go/bin/pkg/mod/github.com/go-sql-driver/mysql@v1.7.1/connection.go:614 +0xaf created by github.com/go-sql-driver/mysql.(*mysqlConn).startWatcher D:/Program Files (x86)/Go/bin/pkg/mod/github.com/go-sql-driver/mysql@v1.7.1/connection.go:611 +0x10a goroutine 83 [select]: github.com/go-redis/redis/v8/internal/pool.(*ConnPool).reaper(0x12f262a0, 0xdf8475800) D:/Program Files (x86)/Go/bin/pkg/mod/github.com/go-redis/redis/v8@v8.11.5/internal/pool/pool.go:485 +0xd6 created by github.com/go-redis/redis/v8/internal/pool.NewConnPool D:/Program Files (x86)/Go/bin/pkg/mod/github.com/go-redis/redis/v8@v8.11.5/internal/pool/pool.go:111 +0x242 goroutine 85 [chan receive]: go-study/models.sendProc(0x12e40cf0) D:/go/go-study/models/Message.go:88 +0x48 created by go-study/models.Chat D:/go/go-study/models/Message.go:79 +0x30d goroutine 86 [IO wait]: internal/poll.runtime_pollWait(0x33340b00, 0x72) D:/Program Files (x86)/Go/src/runtime/netpoll.go:305 +0x52 internal/poll.(*pollDesc).wait(0x138f60f4, 0x72, 0x0) D:/Program Files (x86)/Go/src/internal/poll/fd_poll_runtime.go:84 +0x37 internal/poll.execIO(0x138f6014, 0xa365e0) D:/Program Files (x86)/Go/src/internal/poll/fd_windows.go:175 +0xfc internal/poll.(*FD).Read(0x138f6000, {0x12f41000, 0x1000, 0x1000}) D:/Program Files (x86)/Go/src/internal/poll/fd_windows.go:441 +0x13b net.(*netFD).Read(0x138f6000, {0x12f41000, 0x1000, 0x1000}) D:/Program Files (x86)/Go/src/net/fd_posix.go:55 +0x3f net.(*conn).Read(0x12c0aa68, {0x12f41000, 0x1000, 0x1000}) D:/Program Files (x86)/Go/src/net/net.go:183 +0x4f bufio.(*Reader).fill(0x12d1eae0) D:/Program Files (x86)/Go/src/bufio/bufio.go:106 +0xe9 bufio.(*Reader).Peek(0x12d1eae0, 0x2) D:/Program Files (x86)/Go/src/bufio/bufio.go:144 +0x6d github.com/gorilla/websocket.(*Conn).read(0x12f68000, 0x2) D:/Program Files (x86)/Go/bin/pkg/mod/github.com/gorilla/websocket@v1.5.0/conn.go:371 +0x30 github.com/gorilla/websocket.(*Conn).advanceFrame(0x12f68000) D:/Program Files (x86)/Go/bin/pkg/mod/github.com/gorilla/websocket@v1.5.0/conn.go:809 +0xae github.com/gorilla/websocket.(*Conn).NextReader(0x12f68000) D:/Program Files (x86)/Go/bin/pkg/mod/github.com/gorilla/websocket@v1.5.0/conn.go:1009 +0xb5 github.com/gorilla/websocket.(*Conn).ReadMessage(0x12f68000) D:/Program Files (x86)/Go/bin/pkg/mod/github.com/gorilla/websocket@v1.5.0/conn.go:1093 +0x25 go-study/models.recvProc(0x12e40cf0) D:/go/go-study/models/Message.go:100 +0x105 created by go-study/models.Chat D:/go/go-study/models/Message.go:81 +0x352
06-02

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值