windows IOCP 实践
关于 windows IOCP
有人说 windows IOCP 是 windows 上最好的东西。 IOCP 是真正的异步 IO,意味着每次发起一个 IO 请求,该调用本身则立即返回, 而包括 IO 操作和数据从内核缓冲区到用户缓冲区之间的拷贝都由系统完成,直到这个过程结束系统才通知用户进程。 linux 上没有这样的异步 IO。
IOCP 的使用
- 创建一个新的完成端口。完成端口被设计成与一个线程池相互合作,线程池的线程并发的用来处理完成的 IO 通知。
CreateIoCompletionPort这个 API 用于创建 IOCP, 最后一个参数则是指定线程池中线程个数,一般来说取 CPU * 2 ,这样可以最充分使用多核 CPU ,又降低了线程间的切换。CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, dwNumberOfConcurrentThreads) - 创建工作线程。
- 关联一个 IO 设备到完成端口。也是调用
CreateIoCompletionPort(API 设计有些太随意了吧,难道有什么历史原因?)。HANDLE h = CreateIoCompletionPort(hDevice, hCompletionPort, dwCompletionKey, 0); - 使用 overlapped IO ,例如 socket 的 WSARecv/WSASend,甚至 AcceptEx 和 ConnectEx。这些调用都只是发起一个 IO 请求然后立即返回。函数调用都需要初始化一个 OVERLAPPED 结构体,后面有提到其作用。
- 工作线程是一个 loop, 阻塞在
GetQueuedCompletionStatus调用上。GetQueuedCompletionStatus返回时从 IO 完成队列中取出一个 completion packet。线程池线程阻塞时是由系统负责完成调度的。
IOCP 内部的一些数据结构
- Device List:包含所有与完成端口相关联的设备的一个列表。
- I/O Completion Queue(FIFO):当一个异步 IO 请求完成了,系统会去检查是否这个 IO 设备与任何 IO 完成端口关联了,如果是,系统会在 IO 完成端口队列的末尾添加一个 completion packet(以 FIFO 的顺序入队),
GetQueuedCompletionStatus就是在这个队列上等待。 - IOCP 关联线程等待队列:线程池中的线程调用
GetQueuedCompletionStatus时,就会被放进一个等待队列,IO 完成端口内核对象根据此队列知道有哪些线程在等待处理completion packet。线程等待队列是按照 LIFO 的方式入队的,也就是当有一个 completion packet 到来时,系统先唤醒最后调用GetQueuedCompletionStatus进入等待队列的线程。
IOCP 和线程池的相互作用
- 任何线程都可以调用
GetQueuedCompletionStatus来与一个 IO 完成端口关联起来,但是一个线程只能关联一个 IOCP,当线程退出或者指定了其他的 IOCP或者关闭了 IOCP,线程才与这个 IOCP 解开绑定。 - 创建 IOCP 的时候会指定一个并发值,虽然任意个线程可以关联到这个 IOCP,但是并发值限定了可以同时运行的线程数。假设这样的场景,有一个并发值为 1 的 IOCP,但是有多于一个的线程关联到了这个 IOCP,如果完成队列中总是有一个 completion packet 在等待,当正在运行的线程调用
GetQueuedCompletionStatus时就会立即返回,该线程处理完这个 completion packet 再次调用GetQueuedCompletionStatus又会立即返回。在处理 completion packet过程中,虽然完成队列中始终有 completion packet 待处理,但是因为并发值为 1 的原因,系统不会去调度其他线程来执行,尽管关联 IOCP 的线程不止一个。同时也避免了线程切换的开销,因为始终都是这一个线程在执行。 - 在上述情况中,看起来线程池中关联的其他线程毫无用处,但是其实是没有考虑到正在运行的线程进入等待状态或者因为某种情况与该 IOCP 解除绑定时的情况。如果正在运行的线程调用
Sleep,WaitFor*,或者一个同步 IO 函数,或者任何可以引起当前线程从运行状态变为等待状态的函数时,IOCP 就会立即调度其他关联的线程,维持始终有一个线程在运行。
IOCP 使用过程中遇到的问题
- 因为涉及到多线程会比 epoll + 单线程要编码复杂。
- API 设计比较糟糕,这也加大了编码难度。
- 文档描述不清晰,甚至没有一个官方的示例程序,非官方文档或者程序或多或少有些错误,让人难以放心使用。以 WSARecv 为例子,MSDN上描述若 WSARecv 能立即返回,返回值为 0。这是不是意味着程序要在两处处理 IO 完成的情况,一处是 IO 立即返回时,一处是工作线程
GetQueuedCompletionStatus等待 IOCP 完成队列处。几乎所有的异步 IO 函数都是如此。但是所幸似乎即使立即返回 0 ,完成队列中也会有一个 completion packet,所以只在工作线程中的完成队列中等待 IO 完成也不会出错。 - 一般的使用 TCP 进行通讯的网络程序,因为 TCP 流无界的特性,都会自定义成这样的应用网络数据包:前面几个字节代表该包的长度,后面就是该包真正的内容。应用程序在解包的时候,对应的要先获取包的长度,再截取对应长度的包数据。这样的过程在多线程的 IOCP 会比较困难,多个线程取到了各个数据包的不同部分,而且因为 completion packet 的出队顺序并不能保证,各个线程获取的数据包之间的顺序已经丢失了。因此,必须想办法解决包的顺序问题,而且解包过程需要同步各个线程。这样无疑使得代码变得更复杂。
- IOCP 作为异步 IO ,可以非常方便的发起 IO ,但是每次发起 IO 时候都必须提交一段用户内存,在 IO 完成之前这段内存必须是被锁住的,既你不能再使用。当然这不是 IOCP 的问题,这是异步 IO特性决定的。
一个收发 TCP 应用协议包的程序示例
- 协议包定义成头两个字节保存包长度 len,包头后面 len 字节是包的具体内容。为了简化编码,又能利用到 IOCP 一些特性,决定只启动一个工作线程处理所有的 IO 完成操作,发包和收包都是非阻塞的异步调用。
- 提交给异步 IO 的 buffer,都是从一段预先分配的内存中取出来的,这样使得IO 操作使用的内存是可控的,并且不会有内存碎片,充分使用内存。
- IOCP 的几个核心 API 都与参数 completionKey, overlapped 有关。在程序中 completionKey 可以对应是对哪个 socket 进行操作,overlapped 则对应成具体哪一个 IO 操作。
- 同一时间只允许一个同类的 IO 操作(读或者写)在提交。
代码在此,服务端程序比较简单,可以自己实现并验证。
windows IOCP 实践的更多相关文章
- Docker for Windows(五)实践搭建SqlServer服务&执行数据库操作
上一篇我们已经搭建了一个mysql数据库服务了:Docker for Windows(四)实践搭建&删除MySQL服务,发现用Docker确实是方便且容易,但上一篇主要是服务的搭建删除等基础操 ...
- windows—IOCP
一.重叠I/O回声服务器端 服务端: #include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #d ...
- 【笨嘴拙舌WINDOWS】实践检验之剪切板查看器【Delphi】
该程序能够监视Windows剪切板的内容(文字和图片) 其思路是 先调用SetClipBoardViewer(Self.Handle),让Windows剪切板内容发生改变之后,通知本程序: 然后截获W ...
- 【笨嘴拙舌WINDOWS】实践检验之按键精灵【Delphi】
通过记录键盘和鼠标位置和输入信息,然后模拟发送,就能够创建一个按键精灵! 主要代码如下: library KeyBoardHook; { Important note about DLL memory ...
- 【笨嘴拙舌WINDOWS】实践检验之屏幕取色
实践是检验真理的唯一标准 要取得屏幕的颜色,首先需要创建一个屏幕DC,然后使用该DC,调用GetPixel就可以了 "Note:GetPixel传入的DC应该是屏幕的DC,而不是桌面的DC, ...
- WSL与Windows交互实践
1. WSL是什么 2. WSL新特性 3. WSL管理配置 4. WSL交互 5. 解决方案 * 5.1 使用别名 * 5.2 多复制一份 * 5.3 重定向 * 5.4 symlink 6 ...
- windows IOCP入门的一些资料
最近需要看一个中间件的IOCP处理模块,需要了解这方面的知识 https://www.codeproject.com/articles/13382/a-simple-application-using ...
- Docker for Windows(四)实践搭建&删除MySQL服务
我们已经下载安装好了Docker for Windows:Docker for Windows(一)下载与安装,也简单了解了Docker常用命令:Docker for Windows(三)Docker ...
- 使用 IIS 在 Windows 上托管 ASP.NET Core(Windows安装实践)
原文地址 https://docs.microsoft.com/zh-cn/aspnet/core/host-and-deploy/iis/?view=aspnetcore-2.0&tabs= ...
随机推荐
- 老板居然让我在Java项目中“造假”
1. 前言 老板说,明天甲方要来看产品,你得造点数据,而且数据必须是"真"的,演示效果要好看一些,这样他才会买我们的产品,我好明年给你换个嫂子.一般开发接到这种过分要求都不会很乐意 ...
- 【译】JWT(JSON Web Token) 入门指南
JWT 入门指南 原文地址:https://blog.angular-university.io/angular-jwt/ 这篇文章是两篇手把手教你如何在Angular应用(也适用于企业级应用)中实现 ...
- Axis2开发webservice详解
Axis2开发webservice详解 标签: javawebserviceAxis2 2015-08-10 10:58 1827人阅读 评论(0) 收藏 举报 分类: JAVA(275) 服务器 ...
- Amazing 2020
Amazing 2020 Intro 2020 转眼即逝,2020 是比较艰辛的一年,因为疫情原因,很多人的工作以及生活都多多少少受到了一些影响. 引用网上盛传的一句话--2020 实"鼠& ...
- JVM内存设置多大合适?Xmx和Xmn如何设置?
JVM内存设置多大合适?Xmx和Xmn如何设置? 问题:新上线一个java服务,或者是RPC或者是WEB站点, 内存的设置该怎么设置呢?设置成多大比较合适,既不浪费内存,又不影响性能呢? 分析:依 ...
- 自动化运维工具-Ansible之2-ad-hoc
自动化运维工具-Ansible之2-ad-hoc 目录 自动化运维工具-Ansible之2-ad-hoc Ansible ad-hoc Ansible命令模块 Ansible软件管理模块 Ansibl ...
- 9条消除if...else的锦囊妙计,助你写出更优雅的代码
前言 最近在做代码重构,发现了很多代码的烂味道.其他的不多说,今天主要说说那些又臭又长的if...else要如何重构. 在介绍更更优雅的编程之前,让我们一起回顾一下,不好的if...else代码 一. ...
- python函数----名称空间和作用域
一 名称空间 名称空间即存放名字与对象映射/绑定关系的地方. 对于x=3,Python会申请内存空间存放对象3,然后将名字x与3的绑定关系存放于名称空间中,del x表示清除该绑定关系. 在程序执行 ...
- System类常用方法
System类常用方法 public static long currentTimeMills() 获取当前系统时间,以毫秒值为单位的当前时间. public static void arraycop ...
- idea多模块启动
2018版本的idea 原文链接http://zhhll.icu/2020/04/07/idea/idea%E4%B9%8B%E5%A4%9A%E6%A8%A1%E5%9D%97%E5%90%AF%E ...