|
用户名:hzgmaxwell 笔名:hzgmaxwell 地区: 行业:其他 |
| 日 | 一 | 二 | 三 | 四 | 五 | 六 |
我打磨的小刀
(作者置顶)
| 网络共享监视器 | 监视Windows共享文件的存取记录,并记录详细日志…… |
| Who Are Using Me | 删除文件时,提示有程序在使用它,想知道是谁在使用吗? |
| 文件搜索器refrain | 具备一些Windows自带搜索无法实现的功能,比如: 重复文件搜索 多目录、并排除某些子目录 文件名支持正则表达式 |
- 作者: hzgmaxwell 2005年12月26日, 星期一 13:36 回复(0) | 引用(1) 加入博采
我爱背单词网络版 http://www.oibdc.cn/
- 作者: hzgmaxwell 2008年10月27日, 星期一 14:33 回复(0) | 引用(0) 加入博采
网络共享监视器 更新2006-04-09
感谢各位朋友试用网络共享监视器shareMonitor.exe,能有这么多朋友觉得它还有点用,我很高兴。也有些朋友提出了建议,我根据这些建议做了第三次修改,主要有两点:
http://www.codeproject.com 上有很多巧妙的开放源码,这次读写注册表,我破例不再直接调用win32 api,而是用了来自codeproject的一个模板类 http://www.codeproject.com/system/registry_value.asp ,而写日志的部分,我也直接用了我工作中写出的一个通过类CTraceLog ,以至于这次修改只花了我几分钟。
新的下载链接
- 作者: hzgmaxwell 2006年04月9日, 星期日 17:32 回复(9) | 引用(1) 加入博采
I/O完成端口
I/O完成端口是允许应用程序使用线程池来处理异步I/O请求的机制。线程池中的线程都负责处理I/O请求。相对于收到I/O请求时创建线程,通过I/O完成端口应用程序可以更快更有效的处理异步I/O请求。
CreateIoCompletionPort函数把一个或多个文件句柄关联到一个I/O完成端口。当对其中某个文件的异步I/O操作完成时,一个I/O完成包在相应的I/O完成端口排队。这可以把多个文件的同步点组合到一个对象中。
线程调用GetQueuedCompletionStatus等待一个完成包到达完成端口,而不是直接等待异步I/O操作完成。线程池中所有线程都阻塞在这个函数,当一个完成包到达完成端口时,线程按后入先出(LIFO)的顺序释放。这意味着当一个完成包到达完成端口时,系统释放最后一个在该函数阻塞的线程来处理完成请求。
一个线程调用GetQueuedCompletionStatus之后,它就被绑定到这个完成端口,直到它退出、或者绑定到不同的完成端口、或者调用CloseHandle撤销到该完成端口的绑定。一个线程最多只能绑定到一个完成端口。
完成端口最重要的特性就是并发量,其值是在完成端口创建时指定的。它限定了绑定到本完成端口的线程的可运行个数。当绑定到某完成端口的可运行的线程个数超过并发量,系统会阻塞后续线程的执行,直到可运行线程的个数低于并发量。当完成端口的队列中有完成包在排队,而可运行线程的个数也达到并发量,这时系统是最高效的,因为当一个运行着的线程调用GetQueuedCompletionStatus,它会立刻取得完成包,这样避免了操作系统内部的“线程上下文环境切换”。
一般情况下,并发量设为CPU的个数,但如果对一个完成包的处理是比较耗时的操作,应该设个较大值,具体多少最好由试验值来定。
PostQueuedCompletionStatus函数允许应用程序不通过发起一个异步I/O操作就可以发送一个自定义的完成包,这样就可以把外部事件通知线程池中的工作线程。
完成端口也是通过引用计数来维持它的生命周期,当没有外部引用时,完成端口被释放。所有与完成端口绑定的文件句柄都引用了它,所以在释放完成端口之前,必须释放所有绑定到它的文件句柄。
I/O完成端口是使用线程池的一种机制,除了负责处理异步I/O请求的工作者线程之外,还需要一个主线程创建、管理完成端口和线程池中的工作者线程。
主线程的执行过程如下:
工作者线程的执行过程如下:
整个机制中,最关键的是I/O完成包的流向。
当调用一个异步操作接口(如调用WSARecv、WSASend或者调用ReadFileEx、WriteFileEx去读一个以FILE_FLAG_OVERLAPPED标志打开的文件)时,应用程序就向系统内核发起了一次异步I/O请求。这时需要传递一个LPOVERLAPPED结构和一个LPOVERLAPPED_COMPLETION_ROUTINE回调函数指针(指向异步I/O完成时回调的函数)给异步操作接口作参数。
当系统内核处理完某异步I/O请求后,如果异步I/O请求发起时传入的LPOVERLAPPED_COMPLETION_ROUTINE不为空,该函数会被调用。对于I/O完成端口模型,该值都为空,这样系统内核构造一个I/O完成包把它放入I/O完成端口的队列。
线程池中的工作者线程不断调用GetQueuedCompletionStatus从I/O完成端口的队列中取出I/O完成包,根据完成包的的内容做响应处理。
由此可见,I/O完成端口是个抽象的实体,它拥有(对应、管理)一个线程池(其中包括若干工作者线程)和一个I/O完成包的队列。
同样,I/O完成包也是个抽象实体,它至少对应一个异步I/O操作的目标(可以是文件和Socket)句柄和一个描述一次异步I/O操作的结构,这样工作者线程就有了作出响应的依据。
GetQueuedCompletionStatus的三个输出参数都可以看成是I/O完成包的内容,其中
| BOOL GetQueuedCompletionStatus( [in]HANDLE CompletionPort, [out]LPDWORD lpNumberOfBytes, [out]PULONG_PTR lpCompletionKey, [out]LPOVERLAPPED* lpOverlapped, [in]DWORD dwMilliseconds ); |
lpNumberOfBytes 返回本次异步I/O操作完成传输的字节数,该值由系统内核填充;
lpCompletionKey 返回本次异步I/O操作目标的相关信息,该值返回的仅仅是个内存地址,具体是些什么数据如何分布,由把异步I/O操作目标句柄和完成端口关联时(即主线程在第3步)传给CreateIoCompletionPort的第三个参数指定,即CreateIoCompletionPort第三个参数指向的东西就是lpCompletionKey指向的东西,系统不作任何改动。要注意的是,CreateIoCompletionPort在把多个文件句柄管关联到同一个完成端口时,为每个句柄指定不同的CompletionKey值,内核在完成一次异步I/O操作时,取出相应的CompletionKey值放入I/O完成包。因而在Anthony Jones, Jim Ohlund写的《Network Programming for Microsoft Windows》中,这部分数据被称为Per-handle Data,也就是说这部分数据和完成端口关联的诸多文件句柄是一一对应的。
| HANDLE CreateIoCompletionPort( [in]HANDLE FileHandle, [in]HANDLE ExistingCompletionPort, [in]ULONG_PTR CompletionKey, [in]DWORD NumberOfConcurrentThreads ); |
lpOverlapped 表面上它返回的就是一个LPOVERLAPPED,但在实际应用中通常通过它来传递描述一次异步I/O操作的相关信息,所以该值返回的也仅仅是个内存地址,具体是些什么数据如何分布,与发起异步I/O请求时传给异步API的LPOVERLAPPED/LPWSAOVERLAPPED 参数有关。在Anthony Jones, Jim Ohlund写的《Network Programming for Microsoft Windows》中,这部分数据被称为Per-I/O Operation Data,因为这部分数据和一次异步I/O操作一一对应。和lpCompletionKey不同的是,系统要求传入的lpOverlapped所指向的必须是个LPOVERLAPPED/LPWSAOVERLAPPED 结构,并且也会对这部分进行修改,但对lpOverlapped+sizeof(LPOVERLAPPED)之外的内存不作任何修改。
| typedef struct _PER_IO_DATA { OVERLAPPED Overlapped; WSABUF DataBuf; char buffer[1024]; int BufferLen; int OperationType; }PER_IO_DATA, *LPPER_IO_DATA; typedef struct _PER_HANDLE_DATA { SOCKET Socket; SOCKADDR_STORAGE ClientAddr; }PER_HANDLE_DATA, *LPPER_HANDLE_DATA; LPPER_IO_DATA lpPerIoData = (LPPER_IO_DATA)GlobalAlloc(GPTR, sizeof(PER_IO_DATA)); //这里把Overlapped作为PER_IO_DATA的第一个成员,所以直接转换即可,否则 WSARecv(?,?,?,?,?,&(lpPerIoData->Overlapped),NULL); GetQueuedCompletionStatus取得I/O完成包后,如果Overlapped是PER_IO_DATA的第一个成员,lpOverlapped所指的也就是lpPerIoData的地址,工作者线程可以获取OperationType成员的值;如果Overlapped不是PER_IO_DATA的第一个成员,可以使用CONTAINING_RECORD宏来获取lpPerIoData的地址: lpPerIoData = (LPPER_IO_DATA)CONTAINING_RECORD(lpOverlapped, PER_IO_DATA, Overlapped); |
- 作者: hzgmaxwell 2006年02月28日, 星期二 21:43 回复(1) | 引用(1) 加入博采
优化的艺术
通常有一些数组,我们要根据输入的值变化主、次下标的值,比如:
char map[100][100];
假设有下面这样一段代码:
| int inKey,i,j; switch(inKey) { case 65: if(i<99) i++; break; case 66: if(i>0) i--; break; case 67: if(j<99) j++; break; case 68: if(j>0) j--; break; } |
为了简洁、高效,做下面的优化:
| int inKey,i,j,m,n,step[4][2] = {{1,0},{-1,0},{0,1},{0,-1}}; m = i+step[inKey-65][0]; n = j+step[inKey-65][0]; if(m>=0 && m<100) i = m; if(n>=0 && n<100) j = n; |
通常要对m和n的有效性作检查,确保(0=<m,n<100),而这样的检查也应该放在变换之后来做,如果在变换之前做,需要四个if语句,也就是说即使不设step这个数组变量。这段代码也应该这样来写:
| int inKey,i,j,m,n; switch(inKey) { case 65: m = i+1; break; case 66: m = i-1; break; case 67: n = j+1; break; case 68: n = j-1; break; } if(m>=0 && m<100) i = m; if(n>=0 && n<100) j = n; |
如果既不想多设个辅助变量(如上面的step数组),又不想使用拖沓、低效的switch语句,怎么办?先做一道题。
已知整数inKey与整数step之间的对应关系如下表所示:
| inKey | step |
| 0 | 1 |
| 1 | -1 |
请写出一个满足上述关系的表达式。
这里直接给出答案:2*inKey+step = 1;
对上例就有了:2*(inKey-65)+step = 1;所以step = 131-2*inKey;因而上面的代码也就写成:
| int inKey,i,j,m,n; if(inKey>64 && inKey<67) m = i+131-2*inKey; if(inKey>66 && inKey<69) n = j+135-2*inKey; if(m>=0 && m<100) i = m; if(n>=0 && n<100) j = n; |
这样的优化看起来是不是很美啊!
有时候,输入值和下标差值之间的关系并不是这么明显,但写都应该基于这样的原则来写,才会简洁、高效,当然降低了一点点可读性。
- 作者: hzgmaxwell 2006年02月28日, 星期二 21:36 回复(0) | 引用(1) 加入博采