本文为linux环境下的总结,其他操作系统本质差别不大。本地文件I/O和网络I/O逻辑类似。

epoll+多线程的模型

epoll+多线程模型和epoll 单进程区别、优点

    对比于redis这样典型的epoll+单进程为主的模型,个人理解epoll+多线程模型相对来说,epoll+多线程更利于程序员编写,维护代码,毕竟多线程的模型更符合多数人的逻辑方式。

    例如,单进程下,如果是简单的一问一答方式的服务类型还是OK得,如果服务器对每一个请求都还有至少1次额外的网络I/O操作, 此时如果额外的I/O操作采用同步的方式,无疑将会将主进程阻塞得不偿失,如果也通过事件驱动的方式进行异步I/O,代码的编写和维护成本无疑大大增加。事实上,额外的耗时I/O操作,redis基本都是通过fork一个子进程处理的。

    而多线程的情况下,就可以保证在一部分的请求在I/O阻塞的情况下,服务器还能处理另一部分请求,并且代码写起来更容易,也能好维护。此外多线程也能利用上处理器的多核,但是这方面我没接触过具体的case,体会也不是很明显。

一种简单epoll+多线程模型

    主线程主要负责listen端口并进行access,将acess到的fd放入一个队列里。创建固定个数的处理线程,消费队列里的fd,从fd中读取请求,进行相应处理。处理后的返回结果直接由处理线程返回到客户端 。

    如果处理线程只涉及计算,没有其他阻塞操作的话,不考虑超线程技术的话,创建和CPU核数一样的线程数即可。但是处理线程涉及阻塞操作的话,线程数就要适当增加,以保证时刻都在处理请求而不是全部阻塞。至于要创建多少就要根据实际情况见仁见智了,建少了机器空闲,建多了额外消耗过多的机器资源会反拖累机器处理请求速度。

golang标准网络库通信模型——epoll+协程

    golang的精髓在于协程,个人理解协程的精髓就在于和epoll的配合使得golang能以较低的开发成本,开发出能支持I/O密集场景下的高并发的服务器。(同样场景下,用c/c++理论上肯定能开发出性能更好的服务器,但是目前来看开发成本肯定要高不少)。而计算密集的场景情况下,cpu除了计算还得维护一个go的调度器,显然这并不是go擅长的领域。

    golang自带的网络库的网络通信模型和上述的简单的epoll+多线程模型类似,但是由于golang使用的是协程,拥有自己的调度器,以及能保证创建的内核线程数量不会太多(调度器保持活跃内核线程数和cpu核数一致),使得在内核线程切换带来的消耗大大降低,而用户线程的切换代价和消耗的资源小的多。

golang标准网络库简单探究

对文件描述符fd的封装 —— netFD

    数据结构上只是对socket系统调用返回的fd做一些简单的封装 文件描述符,对应的通信协议等,此外还有一些方法的封装,如将fd加入到epoll里等(epoll_ctl(epfd, EPOLL_CTL_ADD, fd, ..)的封装)

    值得一提,如果不自己在go程序中自己进行epoll_create系统调用的话,一个go程序中只会生成一个epoll实例(通过sync.Once的Do方法实现的)

    所以网路库代码粗略层次大概是这样的(以传输层net包为例)

    net包提供给用户直接使用的方法:提供listen、accept、dail(作为客户端连接)等方法封装,操作的的数据结构是newFD

    newFD(fd_unix.go):对文件描述符的封装,同时也负责提供将fd和多路复用实例关联的方法 (获取newFD 一般是调用go自己封装的一个socket函数)

    netpoll.go:对多路复用实例的封装, 相当于代理层,兼容各种平台

    netpoll_*.go: 不同平台的多路复用的系统调用

lisent 监听端口

    简单来看,listen()时就是调用go自己封装的socket函数,绑定端口并且监听,在go的socket函数里如果没有创建epoll实例的话创建epoll实例(这一步并不是listen特有), 把listen系统调用返回的fd加入到epoll中。

dial 作为客户端发起连接

    dial返回的interface实际上是由Dialer这个struct代理生成的,(这个代理能生成TCP UDP IP UNIX文件对应的conn)

    具体生成过程其实步骤逻辑不算少 但是最后也是调用go自己封装的socket函数, 把生成的连接add到epoll实例里面。

read/accept 读操作

    会直接读对应newFd里存储的系统fd, 因为是noblock,所以read()会直接返回, 如果read系统调用返回的是syscall.EAGAIN, 则会调用gopark()将对应的goruntine挂起(设置_Gwaiting状态)。

write 写操作

    如果第一次write系统调用就将要写入内容全部写入fd,就不会阻塞,否则(写缓冲已满)同样将goruntine挂起。

epoll唤醒被阻塞的goruntine

    阻塞在read() accept() wait()上的协程,会在netpoll函数中被唤醒,注册在epoll里的event都是边缘触发模式(ET),所以在有数据可读的时候,触发可读事件, 而仅当fd从不可写变成可写的时候才会触发可写事件(连接时,写缓冲由满变空闲,对端读取了一些数据) 。

    go的runtime线程在启动时,在有使用网路库的情况才会调用netpoll函数,在linux环境下实际上调用的就是一个for循环不断进行epoll_wait(阻塞调用), 响应事件的回调函数就是调用goready唤醒该fd对应的goruntine(置为runnnable)。每一次epoll_wait最多返回128个事件。

总结

    所以go的标准库网络通信模型其实还是比较简单简洁的,而其核心在其天然支持协程的特性,利用自己的调度器将传统多线程在线程数多的情况造成的系统消耗降低到一个比较令人满意的程度,从而达到一个开放效率和运行效率上的平衡。

go标准库I/O模型:epoll+多协程的更多相关文章

  1. 11.python3标准库--使用进程、线程和协程提供并发性

    ''' python提供了一些复杂的工具用于管理使用进程和线程的并发操作. 通过应用这些计数,使用这些模块并发地运行作业的各个部分,即便是一些相当简单的程序也可以更快的运行 subprocess提供了 ...

  2. Stackful 协程库 libgo(单机100万协程)

    libgo 是一个使用 C++ 编写的协作式调度的stackful协程库, 同时也是一个强大的并行编程库. 设计之初是为高并发分布式Linux服务端程序开发提供底层框架支持,可以让链接进程序的同步的第 ...

  3. libaco: 一个极速的轻量级 C 非对称协程库 🚀 (10 ns/ctxsw + 一千万协程并发仅耗内存 2.8GB + Github Trending)

    0 Name 简介 libaco - 一个极速的.轻量级.C语言非对称协程库. 这个项目的代号是Arkenstone 

  4. gevent实现基于epoll和协程的服务器

    1. 导gevent中的猴子补丁,来把原来python自带的socket变成基于epoll的socket(解除阻塞问题) 代码: # from gevent import monkey;monkey. ...

  5. Python常用的标准库以及第三方库有哪些?

    20个必不可少的Python库也是基本的第三方库 读者您好.今天我将介绍20个属于我常用工具的Python库,我相信你看完之后也会觉得离不开它们.他们是: Requests.Kenneth Reitz ...

  6. Python常用的标准库以及第三方库

    Python常用的标准库以及第三方库有哪些?   20个必不可少的Python库也是基本的第三方库 读者您好.今天我将介绍20个属于我常用工具的Python库,我相信你看完之后也会觉得离不开它们.他们 ...

  7. Python 常用的标准库以及第三方库有哪些?

    作者:史豹链接:https://www.zhihu.com/question/20501628/answer/223340838来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明 ...

  8. 发布一个基于协程和事件循环的c++网络库

    目录 介绍 使用 性能 实现 日志库 协程 协程调度 定时器 Hook RPC实现 项目地址:https://github.com/gatsbyd/melon 介绍 开发服务端程序的一个基本任务是处理 ...

  9. 写个百万级别full-stack小型协程库——原理介绍

    其实说什么百万千万级别都是虚的,下面给出实现原理和测试结果,原理很简单,我就不上图了: 原理:为了简单明了,只支持单线程,每个协程共享一个4K的空间(你可以用堆,用匿名内存映射或者直接开个数组也都是可 ...

随机推荐

  1. A`>G?~C009

    A`>G?~C009 这场怎么才5题...看完猫的提交记录以为猫猫没写这场F A Multiple Array 直接做 B Tournament 直接树d C Division into Two ...

  2. 洛谷 p1968 美元汇率 题解

    传送门 美元由马克转化,马克由美元转化 求最大美元 每一天只有2种选择 ①:不转化另一货币 ②:转化另一货币 典型01背包 可以开一个二维数组f[100][3] F[i][1]表示前i天获得最大美元 ...

  3. @Autowired和@Resource的区别和联系

    背景: 今天下班路上看到一个大货车,于是想到了装配,然后脑海里跳出了一个注解@Autowired(自动装配),于是又想到最近工作项目用的都是@Resource注解来进行装配.于是本着学什么东西都要一钻 ...

  4. Spring Cloud Feign 调用过程分析

    前面已经学习了两个Spring Cloud 组件: Eureka:实现服务注册功能: Ribbon:提供基于RestTemplate的HTTP客户端并且支持服务负载均衡功能. 通过这两个组件我们暂时可 ...

  5. Beef搭建并通过Nginx绑定域名

    Beef和Nginx安装过程这里就不再说明了相关链接:Beef官方安装教程 1. 修改Beef的config.yaml配置文件 xss连接地址改成要绑定的域名 sudo vim /beef/confi ...

  6. 使用ssh-keygen生成私钥和公钥

    1.使用ssh-keygen生成私钥和公钥 命令如下: ssh-keygen -t rsassh-keygen -t rsa -C "用户名自取"可以是邮箱 例子: fdipzon ...

  7. pc端常用电脑屏幕 ((响应式PC端媒体查询)电脑屏幕分辨率尺寸大全)

    PC端************ 按屏幕宽度大小排序(主流的用橙色标明) 分辨率   比例 | 设备尺寸 1024*500 (8.9寸) 1024*768 (比例4:3  | 10.4寸.12.1寸.1 ...

  8. 使用Faker来随机生成接近真实数据的数据

    在很多场景我们需要造一些假数据或者mock数据,如果我们写死类似[XXXX]类似的无意义的其实不是很优雅,Faker能提供常用的一些名词的随机数据. 1.引入POM: <dependency&g ...

  9. nanopi的ds18b20温度传感器测试

    参考(抄袭)资料在这里 先接线,3.3v,gnd,数据输出脚,我是PG11 vim /boot/armbianEnv.txt overlays=w1-gpio param_w1_pin=PG11 pa ...

  10. Django框架入门1虚拟开发环境的配置

    1.安装virtualenv虚拟程序 C:\Users\ws>pip install virtualenv 创建名字为testvir的虚拟环境 C:\Users\ws>virtualenv ...