

网络并发编程模型
source link: https://hiberabyss.github.io/2018/03/20/concurrency-models/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

网络并发编程模型一般包括三种:
- IO 多路复用
本文会着重介绍 IO 多路复用这种编程模型, 包括对 select、poll、 epoll 的介绍.
多进程和多线程并发编程模型
多进程和多线程并发编程模型都是通过新建进程或者线程去处理新的网络连接请求. 因为创建新进程的开销比要比线程高很多, 一般会使用多线程来实现并发.
如果把 golang 的协程看作是用户态线程的话, 我们可以用下面的代码来说明 多进程/多线程 编程模型:
package main
import (
"fmt"
"net"
)
func connHandler(c net.Conn) {
buf := make([]byte, 1024)
defer c.Close()
cnt, _ := c.Read(buf)
c.Write(buf[0:cnt])
}
func main() {
server, _ := net.Listen("tcp", ":1208")
fmt.Println("Server started ...")
for {
conn, _ := server.Accept()
go connHandler(conn)
}
}
如果是多进程并发编程模型的话, 就会用 fork 创建子进程去处理新的连接.
因为在系统中创建进程或者线程的数量是受限的, 也导致基于多进程多线程模型的并发数收到了限制.
IO 多路复用
在 Linux/Unix 系统中, 对网络进行了抽象, 一个网络连接可以用一个描述符来表示.
而 IO 多路复用就是通过某种机制, 使得一个进程或线程可以监视多个描述符, 当某个描述符就绪时, 就能通知程序进行相应的读写操作, 也就是接收或者发送网络数据.
当我们进行一次 IO 访问时, 例如 read , 一般会发生下面两个过程:
- 等待数据准备; 数据从网卡等设备拷贝到操作系统内核缓冲区;
- 将数据从内核缓冲区拷贝到进程地址空间内存;
IO 多路复用包括三种: select、poll、epoll, 它们的实现原理都是类似的:
- 先设置一些列的描述符;
- select 等函数会不断地轮询所负责的所有描述符, 当某个描述符准备好时就直接返回, 通知用户程序, 否则就阻塞;
具体如下图所示:
select 缺点
select
函数定义:
int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
- 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很
- 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
- 支持的描述符数量受宏变量
FD_SETSIZE
限制, 默认比较小, 32 系统下是1024
, 64 位系统下是2048
int poll (struct pollfd *fds, unsigned int nfds, int timeout);
poll 本质上和 select 没有区别, 只是描述 fd 集合的方式不同, poll 使用的是 pollfd
而 select 使用的是 fd_set
方式. pollfd
底层是基于链表实现的.
poll 没有最大并发连接数的限制.
epoll 优点
int epoll_create(int size);//创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
- 没有最大并发连接的限制,能打开的fd上限远大于1024(1G的内存上能监听约10万个端口)
- 采用回调的方式, 效率提升. 只有活跃可用的fd才会调用callback函数, 也就是说 epoll 只管 “活跃” 的连接, 而跟连接总数无关.
- 利用 mmap 文件映射内存加速与内核空间的消息传递, 避免了每次调用 select 都要进行内核态数据和用户数据的拷贝.
epoll
适用于存在大量 idle -connection 或者 dead-connection 的场景.
epoll 工作模式
epoll对文件描述符的操作有两种模式:LT(level trigger,水平触发)和ET(edge trigger,边缘触发)。二者的区别如下:
- 水平触发:默认工作模式,即当 epoll_wait 检测到某描述符事件就绪并通知应用程序时,应用程序可以不立即处理该事件;下次调用epoll_wait时,会再次通知此事件。
- 边缘触发:当epoll_wait检测到某描述符事件就绪并通知应用程序时,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次通知此事件。(直到你做了某些操作导致该描述符变成未就绪状态了,也就是说边缘触发只在状态由未就绪变为就绪时通知一次)
边缘触发(ET模式)在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞socket,以避免由于一个文件描述符的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。
这里 有更详细的三者间的对比以及原理介绍.
References
Recommend
-
46
传统的并发编程我们都很熟悉,以 Java 举例,那就是通过共享内存和锁的方式来进行并发控制。但你有没有想过还有其他方式可以实现高并发呢?今天...
-
31
前言 在开始学习 Go 特性的并发编程之前,先来简单了解一些基础概念,这对理解学习 Go 并发模型会有一些帮助。 概念 内核态 也被称为内核空间,是系统内核的运行空间,与用户空间隔离。控制计...
-
9
Week03: Go并发编程(二) Go 内存模型 mohuishou _ 2020年12月7日 下午 3.8k 字 46 分钟本系列为极客时间 Go...
-
27
-
11
Java 并发编程 ④ - Java 内存模型 Written by Richard_Yi with ♥ on 2020年04月24日 in...
-
9
让我们通过本篇文章一同进入并发编程技术的世界里面,相信通过这篇文文章一定会对话你的并发技术体系有一定帮助以及夯实你的基础功底。 并发concurrency 并行parallelism 吞吐量throughput...
-
5
前言:Java内存模型详细说明了Java虚拟机(以下将使用JVM这个术语)是如何与计算机的内存(RAM)进行交互的。JVM是一个完整计算机的模型,所以该模型也包含了内存模块,也就是我们熟知的Java内存模型。想要设计出符合预期的并发程序,了解Java的内存模型是非...
-
5
Go设计与原理 CSP 并发编程模型
-
7
一、通过程序看现象 在开始为大家讲解Java 多线程缓存模型之前,我们先看下面的这一段代码。这段代码的逻辑很简单:主线程启动了两个子线程,一个线程1、一个线程2。线程1先执行,sleep睡眠2秒钟之后线程2执行。两个线程使用到了一个...
-
6
【2-1 Golang】Go并发编程—GMP调度模型概述 tomato01 · 4分钟之前 · 75...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK