欧美三区_成人在线免费观看视频_欧美极品少妇xxxxⅹ免费视频_a级毛片免费播放_鲁一鲁中文字幕久久_亚洲一级特黄

《Windows核心編程系列》十異步IO之IO完成端口

系統(tǒng) 2306 0

http://blog.csdn.net/ithzhang/article/details/8508161 轉(zhuǎn)載請注明出處!!

???????????? IO 完成端口

為了將 Windows 打造成一個出色的服務(wù)器環(huán)境, Microsoft 開發(fā)出了 IO 完成端口。完成端口需要與線程池配合使用。

完成端口背后的理論是并發(fā)運行的線程數(shù)量必須有一個上限。由于太多的線程將會導(dǎo)致系統(tǒng)花費很大的代價在各個線程 cpu 上下文進行切換。

使用并發(fā)模型與創(chuàng)建進程相比開銷要低很多,但是也需要為每個客戶請求創(chuàng)建一個新的線程。這開銷仍然很大。通過使用線程池可以是性能有很大的提高。 IO 完成端口需要配合線程池配合使用。

IO 完成端口也是一個內(nèi)核對象。調(diào)用以下函數(shù)創(chuàng)建 IO 完成端口內(nèi)核對象。

    HANDLE CreateIoCompletionPort(

      HANDLE hFile,

      HANDLE hExistingCompletionPort,

      ULONG_PTR CompletionKey,

      DWORD dwNumberOfConcurrentThreads);


  


這個函數(shù)會完成兩個任務(wù):

一是創(chuàng)建一個 IO 完成端口對象。

二是將一個設(shè)備與一個 IO 完成端口關(guān)聯(lián)起來。

hFile 就是設(shè)備句柄。

hExistingCompletionPort 是與設(shè)備關(guān)聯(lián)的 IO 完成端口句柄。為 NULL 時,系統(tǒng)會創(chuàng)建新的完成端口。

dwCompletionKey 是一個對我們有意義的值,但是操作系統(tǒng)并不關(guān)心我們傳入的值。一般用它來區(qū)分各個設(shè)備。

dwNumberOfConcurrentThreads 告訴 IO 完成端口在同一時間最多能有多少進程處于可運行狀態(tài)。如果傳入 0 ,那么將使用默認值(并發(fā)的線程數(shù)量等于 cpu 數(shù)量)。在第二章我們曾介紹說幾乎所有的內(nèi)核對象都需要安全屬性參數(shù)。那時的幾乎就是因為 IO 完成端口這個例外。它是唯一一個不需要安全屬性的內(nèi)核對象。這是因為 IO 完成端口在設(shè)計時就是只在一個進程中使用。

每次調(diào)用 CreateIoCompletionPort 時,函數(shù)會判斷 hExistingCompletionKey 是否為 NULL ,如果為 NULL ,會創(chuàng)建新的完成端口內(nèi)核對象。并為此完成端口創(chuàng)建設(shè)備列表然后將設(shè)備加入到此完成端口設(shè)備列表中(先入先出)。

設(shè)備列表存儲與該完成端口相關(guān)聯(lián)的所有設(shè)備。

設(shè)備列表只是調(diào)用 CreateIoCompletionPort 函數(shù)時的一個數(shù)據(jù)結(jié)構(gòu)。除此之外還有四個結(jié)構(gòu)。

第二個結(jié)構(gòu)是 IO 完成隊列。當設(shè)備的一個異步 IO 請求完成時,系統(tǒng)會檢查該設(shè)備是否與一個完成端口相關(guān)聯(lián),如果關(guān)聯(lián),系統(tǒng)會將這個已完成的 IO 請求添加到完成端口的 IO 完成隊列中。每一項包括已傳輸字節(jié)數(shù),完成鍵( dwCompletionKey )值,以及一個指向 IO 請求的 OVERLAPPED 結(jié)構(gòu)指針和錯誤碼。

Windows IO 完成端口提供了一個函數(shù),可以將線程切換到睡眠狀態(tài),來等待設(shè)備 IO 請求完成并進入完成端口。

    BOOL GetQueuedCompletionStatus(

     HANDLE hCompletionPort,

     PDWORD pdwNumberOfBytesTransferred,

     ULONG_PTR pCompletionKey,

     OVERLAPPED** ppOverlapped,

     DWORD dwMilliSeconds);


  


hCompletionPort 表示線程希望對哪個完成端口進行監(jiān)視, GetQueuedCompletionStatus 的任務(wù)就是將調(diào)用線程切換到睡眠狀態(tài),也就是阻塞在此函數(shù)上,直到指定的 IO 完成端口出現(xiàn)一項或者超時。

pdwNumberOfBytesTransferred返回在異步IO完成時傳輸?shù)淖止?jié)數(shù)。

pCompletionKey返回完成鍵。

ppOverlapped返回異步IO開始時傳入的OVERLAPPED結(jié)構(gòu)地址。

dwMillisecond指定等待時間。

函數(shù)執(zhí)行成功則返回true,否則返回false。

第三個結(jié)構(gòu)是等待線程隊列。當線程池中的每個線程調(diào)用 GetQueuedCompletionStatus 時,調(diào)用線程的線程標識符會被添加到這個等待線程隊列,這使得 IO 完成端口對象能知道,有哪些線程當前 正在等待 對已完成的 IO 請求進行處理。當 IO 完成端口的 IO 完成隊列中 出現(xiàn)一項時,完成端口會喚醒 等待線程隊列 中的一個線程。這個線程會得到已完成 IO 項的所有信息:已傳輸字節(jié)數(shù),完成鍵以及 OVERLAPPED 結(jié)構(gòu)地址。這些信息是通過傳給 GetQueuedCompletionStatus 的參數(shù)來返回的。

IO 完成隊列中的各項是以先入先出方式來進行的。但是喚醒等待隊列中的線程是按照后入先出的方式進行。假設(shè)有四個線程正在等待隊列中等待,如果出現(xiàn)了一個已完成的 IO 項,那么最后一個由于調(diào)用 GetQueuedCompletionStatus 而被掛起的線程會被喚醒來處理這一項。當處理完該項后,線程會由于再次調(diào)用 GetQueuedCompletionStatus 而進入等待線程隊列。使用這種算法,系統(tǒng)可以將哪些長時間睡眠的線程換出到磁盤。

作者一直在推崇 IO 完成端口。接下來我們來討論下為什么 IO 完成端口這么有用!!!

前面我們提到過 IO 完成端口只有配合線程池才能發(fā)揮更大的作用。當我們創(chuàng)建并關(guān)聯(lián)設(shè)備時,需要指定有多少個線程并發(fā)運行。一般將這個值設(shè)置為 cpu 的數(shù)量。當已完成的 IO 項被添加到完成隊列中時, IO 完成端口會喚醒正在等待的線程,但是喚醒的線程數(shù)最多不會超過我們指定的數(shù)量。如果有四個 IO 請求已完成,且有四個線程等待 GetQueuedCompletionStatus 而被掛起,那么 IO 完成端口只喚醒兩個線程處理,另外兩個線程繼續(xù)睡眠。此時讀者可能會疑問:既然完成端口只允許喚醒指定數(shù)量的線程,那么為什么還指定更多的線程在線程池中呢?這就涉及到 IO 完成端口的第四個數(shù)據(jù)結(jié)構(gòu):已釋放線程列表。它存儲已被喚醒的線程句柄。這使得 IO 完成端口能夠直到哪些線程已經(jīng)被喚醒并監(jiān)視它們的執(zhí)行情況。如果此時已釋放線程由于調(diào)用某些函數(shù)將線程切換到了等待狀態(tài),完成端口會將其從已釋放隊列中移除,并將其添加到已暫停線程列表。

已暫停隊列是 IO 完成端口第五個數(shù)據(jù)結(jié)構(gòu)。

完成端口的目標是根據(jù)創(chuàng)建完成端口時指定的并發(fā)線程數(shù)量,將盡可能多的線程保持在已釋放線程列表中。如果一個已暫停的線程被喚醒,它會離開已暫停線程列表并重新進入已釋放線程列表。 這意味著已釋放列表中的線程數(shù)量將大于最大允許的并發(fā)線程數(shù)量。 這句話什么意思呢?正在運行的線程數(shù)量加上從暫停線程列表中被釋放的線程數(shù)量使總數(shù)大于最大允許的數(shù)量。這可以使線程數(shù)量在短時間內(nèi)超過指定數(shù)量。

IO 完成端口并不一定要用于設(shè)備 IO ,它還可以進行線程間通信。在可提醒 IO 中我們介紹了 QueueUserAPC 。該函數(shù)允許線程將一個 APC 項添加到另一個線程的隊列中。 IO 完成端口也存在一個類似的函數(shù):

>IO 通知追加到 IO 完成端口?size:14pt">

    BOOL PostQueuedCompletionStatus(

     HANDLE hCompletionPort,

     DWORD dwNumBytes,

     ULONG_PTR CompletionKey,

     OVERLAPPED*pOverlapped);


  


這個函數(shù)用來將已完成的 IO 通知追加到 IO 完成端口的隊列中。

hCompletionPort 表示我們要將已完成的 IO 項添加到哪個完成端口的隊列中。

剩下的三個參數(shù)表示應(yīng)該返回給主調(diào)線程的值。

?每個線程都調(diào)用一次 GetQueuedCompletionStatus ,將它們都喚?E7??程通信。例如:當用戶終止服務(wù)程序時,我們想要所有線程退出。如果各個線程還在等待完成端口但有沒有已完成的 IO 請求,那么無法將它們喚醒。我們可以為線程池中的每個線程都調(diào)用一次 GetQueuedCompletionStatus ,將它們都喚醒。各線程對 GetQueuedCompletionStatus 函數(shù)返回值進行檢查,如果發(fā)現(xiàn)應(yīng)用程序正在終止,就會正常退出。(由于線程等待隊列是以棧方式喚醒各線程,為了保證線程池中每個線程都有機會得到模擬 IO 項,我們還必須在程序中采用其他線程同步機制)

當對完成端口調(diào)用 CloseHandle 時,系統(tǒng)會將所有正在等待 GetQueuedCompletionStatus 返回的線程喚醒,并返回 false GetLastError 返回 ERROR_INVALID_HANDLE ,此時線程就可以知道應(yīng)該退出了。

下面的例子使用異步IO 實現(xiàn)了文件復(fù)制工作。首先選擇要復(fù)制的文件,并取得源文件大小。點擊復(fù)制按鈕創(chuàng)建一個線程。新線程將完成創(chuàng)建IO完成端口和關(guān)聯(lián)設(shè)備及執(zhí)行文件復(fù)制工作。將源文件和目標文件都關(guān)聯(lián)到同一個完成端口,根據(jù)GetQueuedCompletionStatus返回時的完成鍵來區(qū)分到底是屬于誰的異步IO返回。在啟動循環(huán)時采用了一個小伎倆:使用PostQueuedCompletionStatus向IO完成端口發(fā)送一個模擬的異步IO請求。完成鍵設(shè)置為WRITE_KEY。此時程序?qū)?zhí)行從源文件讀數(shù)據(jù)操作。這樣就開動了引擎。直到文件復(fù)制完成。注意源文件和目標文件以及GetQueuedCompletionStatus使用的OVERLAPPED結(jié)構(gòu)不要使用同一個。
選擇文件函數(shù):
        void CIOCompletionPortDlg::OnBnClickedBtnChoosefile()
{
	// TODO: 在此添加控件通知處理程序代碼
	CFileDialog dlg(true);
	dlg.DoModal();
	m_fileName=dlg.GetPathName();
	SetDlgItemText(IDC_EDIT_FILENAME,m_fileName);

	m_hSrcFile=CreateFile(m_fileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_ALWAYS,FILE_FLAG_OVERLAPPED,NULL);
	if(m_hSrcFile==INVALID_HANDLE_VALUE)
	{
		return ;
	}
	DWORD filesize;
	DWORD filesizeHigh;
	m_SrcFileSize=GetFileSize(m_hSrcFile,&filesizeHigh);
	DWORD t=m_SrcFileSize/1024.0;
	//filesize/=1024.0;
	CString temp;
	temp.Format(TEXT("%d KB"),t);
	SetDlgItemText(IDC_EDIT_FILESIZE,temp);
}
      
//創(chuàng)建任務(wù)線程:
        void CIOCompletionPortDlg::OnBnClickedBtnCopy()
{
	// TODO: 在此添加控件通知處理程序代碼

	m_hCopyThread=CreateThread(NULL,0,CopyThread,this,0,NULL);
	//CloseHandle(m_hCopyThread);

}
      

//新線程入口函數(shù):
        
          DWORD WINAPI CIOCompletionPortDlg::CopyThread( PVOID ppram )
{
	CIOCompletionPortDlg *pdlg=(CIOCompletionPortDlg*)ppram;
	pdlg->m_hDesFile=CreateFile(TEXT("備份.exe"),GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_FLAG_OVERLAPPED,pdlg->m_hSrcFile);
	LARGE_INTEGER filesize;
	filesize.HighPart=0;
	filesize.LowPart=pdlg->m_SrcFileSize;
	SetFilePointerEx(pdlg->m_hDesFile,filesize,NULL,FILE_BEGIN);
	SetEndOfFile(pdlg->m_hDesFile);


	//創(chuàng)建IO完成端口。創(chuàng)建一個完成端口,將兩個設(shè)備將關(guān)聯(lián)到此完成端口上。
	HANDLE hIOCP=CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,4);//創(chuàng)建完成端口,但不關(guān)聯(lián)設(shè)備。
	if(hIOCP==NULL)
	{
		return 0;
	}
	CreateIoCompletionPort(pdlg->m_hSrcFile,hIOCP,READ_KEY,0);//與IO完成端口關(guān)聯(lián)。
	CreateIoCompletionPort(pdlg->m_hDesFile,hIOCP,WRITE_KEY,0);//在關(guān)聯(lián)時傳入了完成鍵。可以根據(jù)完成鍵來區(qū)別從完成隊列中取出的請求屬于哪個設(shè)備。

	OVERLAPPED ov={0};
	PostQueuedCompletionStatus(hIOCP,0,WRITE_KEY,&ov);//發(fā)送模擬完成異步IO消息。
	BYTE *pBuffer=new BYTE[BUFFERSIZE];
	OVERLAPPED ovDes={0};
	OVERLAPPED ovSrc={0};
	while(true)
	{
		//memset(pBuffer,0,sizeof(pBuffer));
		DWORD nTransfer;
		OVERLAPPED *overlapped;
		ULONG_PTR CompletionKey;
		GetQueuedCompletionStatus(hIOCP,&nTransfer,&CompletionKey,(OVERLAPPED**)&overlapped,INFINITE);//IO完成隊列沒有請求項則掛起。否則從IO完成隊列取出。
		switch(CompletionKey)
		{
		case READ_KEY://從IO完成端口取出讀完成。
			{          		                                		                                BOOL r=WriteFile(pdlg->m_hDesFile,pBuffer,overlapped->InternalHigh,NULL,&ovDes);

				ovDes.Offset+=BUFFERSIZE;
			}
			break;
		case WRITE_KEY://從IO完成隊列中取出寫完成。
			{
				memset(pBuffer,0,BUFFERSIZE);
				if(ovSrc.Offset<pdlg->m_SrcFileSize)
				{
					DWORD nBytes;
					if(ovSrc.Offset+BUFFERSIZE<pdlg->m_SrcFileSize)
						nBytes=BUFFERSIZE;
					else
						nBytes=pdlg->m_SrcFileSize-ovSrc.Offset;
					ReadFile(pdlg->m_hSrcFile,pBuffer,nBytes,NULL,&ovSrc);//異步IO忽略文件指針。所有對文件的定位操作由OVERLAPPED結(jié)構(gòu)指定。
					//一定要注意為每次異步IO請求提供一個OVERLAPPED結(jié)構(gòu)。剛才由于在接收和發(fā)送使用了
					//同一個OVERLAPPED結(jié)構(gòu),導(dǎo)致出現(xiàn)重疊 I/O 操作在進行中。錯誤代碼:997
					ovSrc.Offset+=BUFFERSIZE;//OVERLAPPED的OffsetHigh結(jié)構(gòu)必須每次都得設(shè)置。
				}
				else
				{
					::MessageBox(NULL,TEXT("文件復(fù)制完成"),TEXT(""),MB_OK);
					return 0;
				}

			}
			break;
		default:
			break;
		}
	}
	return 0;
}
        
      

運行結(jié)果:

《Windows核心編程系列》十異步IO之IO完成端口


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯(lián)系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發(fā)表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 日韩一级在线播放 | 欧美 日韩 中文 | 久草在线观看首页 | 亚洲国产精品成 | 天天色天天碰 | 欧美性色综合网 | 制服丝袜成人动漫 | 国产一区二区视频在线播放 | 亚洲精品欧美视频 | 久草免费在线视频 | 超级碰碰碰视频视频在线视频 | 嫩草影院国产 | 国产午夜精品在线 | 亚洲免费在线观看视频 | 日韩在线中文字幕 | 久草福利资源网站免费 | 免费观看呢日本天堂视频 | 久久国产美女 | 久久国产欧美日韩精品 | 日韩在线欧美 | 免费看日韩A片无码视频软件 | 午夜精品一区二区三区在线观看 | 福利视频第一页 | 奇米第七色 | 国产在线小视频 | 亚洲宗合 | 久久av网| 亚洲精品aⅴ | 久久久精品午夜免费不卡 | 福利视频区 | 亚洲欧美精品一区二区 | 日韩av片免费播放 | 国产视频久久 | 日韩二区 | 野花国产精品入口 | 国产精品视频1区 | 欧美伊人久久综合网 | 国产精品入口免费视频 | 日韩在线操 | 亚瑟天堂久久一区二区影院 | 国产亚洲精品一品区99热 |