青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

sherrylso

C++博客 首頁 新隨筆 聯(lián)系 聚合 管理
  18 Posts :: 0 Stories :: 124 Comments :: 0 Trackbacks

2011年2月18日 #

Java通過JNI機(jī)制調(diào)用c/c++寫的native程序。c/c++開發(fā)的native程序需要遵循一定的JNI規(guī)范,下面的例子就是一個(gè)JNI函數(shù)聲明:
JNIEXPORT jint JNICALL Java_jnitest_MyTest_test
  (JNIEnv 
* env, jobject obj, jint arg0);
JVM負(fù)責(zé)從Java Stack轉(zhuǎn)入C/C++ Native Stack。當(dāng)Java進(jìn)入JNI調(diào)用,除了函數(shù)本身的參數(shù)(arg0),會(huì)多出兩個(gè)參數(shù):JNIEnv指針和jobject指針。
JNIEnv指針是JVM創(chuàng)建的,用于Native的c/c++方法操縱Java執(zhí)行棧中的數(shù)據(jù),比如Java Class, Java Method等。

首先,JNI對(duì)于JNIEnv的使用, 提供了兩種語法: c語法以及c++語法,如下:
c語法:
jsize len = (*env)->GetArrayLength(env,array);
c++語法:
jsize len =env->GetArrayLength(array);
(注:由于C語言并不支持對(duì)象的概念,所以C語法中需要把env作為第一個(gè)參數(shù)傳入,類似于C++的隱式參數(shù)this指針).


另外: JNIEnv有幾個(gè)設(shè)計(jì)的原則:
第一、JNIEnv指針被設(shè)計(jì)成了Thread Local Storage(TLS)變量,也就是說每一個(gè)Thread, JNIEnv變量都有獨(dú)立的Copy。這樣做的原因主要是考慮到:
由于JVM要運(yùn)行在多個(gè)平臺(tái)(除了主流的Windows,Linux等平臺(tái)),JNI內(nèi)部實(shí)現(xiàn)很多要依賴到TLS, 為了減少對(duì)TLS的依賴,所有TLS based的數(shù)據(jù)都會(huì)存放于JNIEnv中。這樣相當(dāng)于只依賴一個(gè)TLS based的變量JNIEnv。由于JNIEnv指針是TLS的,所以你不能把Thead#1使用的JNIEnv傳給Thread#2使用。

第二、JNIEnv中定義了一組函數(shù)指針,c/c++ Native程序是通過這些函數(shù)指針操縱Java數(shù)據(jù)。這樣設(shè)計(jì)的好處是:你的c/c++ 程序不需要依賴任何函數(shù)庫,或者DLL。由于JVM可能由不同的廠商實(shí)現(xiàn),不同廠商有自己不同的JNI實(shí)現(xiàn),如果要求這些廠商暴露約定好的一些頭文件和庫,這不是靈活的設(shè)計(jì)。
而且使用函數(shù)指針表的另外一個(gè)好處是: JVM可以根據(jù)啟動(dòng)參數(shù)動(dòng)態(tài)替換JNI實(shí)現(xiàn)。比如:類似于C庫,JNI實(shí)現(xiàn)為了性能起見,并沒有對(duì)調(diào)用者傳入的參數(shù)進(jìn)行檢查。但是在調(diào)試階段,也許這種檢查是很必要的,幫助你盡早發(fā)現(xiàn)BUG。例如如果你使用IBM JDK,你可以指定JVM參數(shù)–Xcheck:jni,告訴JVM使用帶檢查的JNI實(shí)現(xiàn)。

參考:
http://java.sun.com/docs/books/jni/html/jniTOC.html
posted @ 2011-02-18 10:59 愛上龍卷風(fēng) 閱讀(8057) | 評(píng)論 (1)編輯 收藏

2009年8月7日 #

DLL中導(dǎo)出函數(shù)有兩種方式,即:dllexport與.def文件。
dllexport方式是:在函數(shù)聲明中加上__declspec(dllexport);
.def方式是:采用模塊定義(.def)文件聲明,(.def)文件為鏈接器提供了有關(guān)被鏈接程序的導(dǎo)出、屬性及其他方面的信息。
(關(guān)于def文件,可以參考http://msdn.microsoft.com/en-us/library/d91k01sh(VS.80).aspx)
對(duì)于這兩種方式,需要特別說明的是:
 第一、用.def文件導(dǎo)出的函數(shù),其名稱是按我們的意愿定義的,而用__declspec(dellexport)導(dǎo)出時(shí),會(huì)有相應(yīng)的修飾名,具體的話,根據(jù)不同的編譯器其修飾名也不一樣。

 第二、__declspec(dllexport)定義的導(dǎo)出多用于同一編譯器的隱式鏈接(靜態(tài)調(diào)用),而.def導(dǎo)出函數(shù)可以確定導(dǎo)出的函數(shù)名不會(huì)因?yàn)椴煌木幾g器而不同,可用于其它開發(fā)工具的調(diào)用。

有了上面的知識(shí),我們?cè)倏碕NI環(huán)境下的問題。

JNI定義了關(guān)鍵字JNIEXPORT,用于實(shí)現(xiàn)DLL中函數(shù)的導(dǎo)出的。實(shí)際在JNI中,JNIEXPORT被定義為,#define JNIEXPORT __declspec(dllexport),也就是說JNI默認(rèn)的導(dǎo)出函數(shù)使用dllexport方式。我們知道,使用使用dllexport方式產(chǎn)生的導(dǎo)出函數(shù)名會(huì)根據(jù)編譯器發(fā)生變化,在這種情況下,當(dāng)Java程序通過Native接口調(diào)用DLL本地方法時(shí),可能會(huì)發(fā)生找不到導(dǎo)出函數(shù)的問題。所以,在JNI的情況下,因此最好是定義一個(gè).def文件來指明導(dǎo)出函數(shù),以避免發(fā)生UnSatisfiedLinkedException錯(cuò)誤 。




posted @ 2009-08-07 22:44 愛上龍卷風(fēng) 閱讀(2638) | 評(píng)論 (1)編輯 收藏

2009年3月12日 #

static關(guān)鍵字,有兩個(gè)作用:
1) 作用于局部變量,定義該變量的存儲(chǔ)方式,就是我們常常說的靜態(tài)局部變量。
2) 作用于用于模塊內(nèi)聲明的變量和函數(shù),用于指示其可見性。

先談?wù)勛兞亢秃瘮?shù)的可見性。在默認(rèn)的情況下,模塊內(nèi)聲明的變量和函數(shù)是全局可見的,如下:
//Test1.cpp
struct {  
    
int m;
} test;
//Test2.cpp
struct {  
    
int m;
} test;
BUILD的結(jié)果會(huì)報(bào)"multiply defined symbols found"
如果想避免這樣的錯(cuò)誤,需要使用static關(guān)鍵字。

不過好像如果你使用typedef,就不會(huì)出現(xiàn)這樣的錯(cuò)誤。
//Test1.cpp
typedef struct {  
    
int m;
} test;
//Test2.cpp
typedef struct {  
    
int m;
} test;
或者:
//Test1.cpp
typedef struct {  
    
int m;
} test;
//Test2.cpp
typedef int test;
關(guān)于這一點(diǎn),我想可能是typedef的作用域是限定在模塊內(nèi)的,所以沒有這個(gè)問題。這個(gè)使用VC和使用GCC都沒問題,不過我沒有查到官方的文檔。

最后一點(diǎn),想說的是,關(guān)于static變量的生命周期。一般來說:static聲明的變量初始化,只是在程序運(yùn)行的第一次被執(zhí)行。不過有例外,那就是如果該變量定義在dll內(nèi),那么該變量的初始化工作是在dll被裝載時(shí)執(zhí)行,在這種情況下,程序雖然只運(yùn)行一次,但是該靜態(tài)變量可能會(huì)被初始化好幾次(與dll被裝載的次數(shù)有關(guān)). 這個(gè)問題本質(zhì)上是:static聲明的變量的生命周期與包含它的組件相關(guān)。從這個(gè)意義上,我們可以說,static變量在不同的包含組件(EXE或DLL)上表現(xiàn)出不同行為,移植能力差,容易造成BUG,這樣的BUG也不容易發(fā)現(xiàn)。我們?cè)陂_發(fā)程序的時(shí)候,不可能去假設(shè)該靜態(tài)變量是被包含在什么樣的組件里。我想,這也是一個(gè)不鼓勵(lì)使用static變量的一個(gè)重要原因。







posted @ 2009-03-12 18:12 愛上龍卷風(fēng) 閱讀(2138) | 評(píng)論 (2)編輯 收藏

2009年2月18日 #

       Windows Nativec++應(yīng)用大量使用了DLL技術(shù)。"動(dòng)態(tài)鏈接"這幾字指明了DLLs是如何工作的。對(duì)于常規(guī)的函數(shù)庫,鏈接器從中拷貝它需要的所有庫函數(shù),并把確切的函數(shù)地址傳送給調(diào)用這些函數(shù)的程序。而對(duì)于DLLs,函數(shù)儲(chǔ)存在一個(gè)獨(dú)立的動(dòng)態(tài)鏈接庫文件中。在創(chuàng)建Windows程序時(shí),鏈接過程并不把DLLs文件鏈接到程序上。直到程 序運(yùn)行并調(diào)用一個(gè)DLLs中的函數(shù)時(shí),該程序才要求這個(gè)函數(shù)的地址。此時(shí)Windows才在DLLs中尋找被調(diào)用函數(shù),并把它的地址傳送給調(diào)用程序。采用這種方法,DLLs達(dá)到了復(fù)用代碼的極限。

      對(duì)于DLL, 關(guān)鍵一點(diǎn)是,所有run on windows system 的程序可以共用同一個(gè)DLL庫,從而達(dá)到最大限度的代碼復(fù)用。并且,由于DLL并不拷貝它需要的所有庫函數(shù)  這樣的話NativeC++程序 executable image size 會(huì)比較小。

      modularity的角度,如果要在Java的應(yīng)用里尋找相對(duì)應(yīng)的DLL的概念,我們會(huì)自然地想到jar包。JAR包可以被 Class Loader動(dòng)態(tài)裝載進(jìn)JVM, 不過要幾點(diǎn)區(qū)別需要說明的是:

第一、從本質(zhì)上來講,JAR包是存在于磁盤上的一些data而已(JVM解釋執(zhí)行),而DLLexecutable image

第二、Class Data Sharing (CDS)作為一個(gè)新的feature,Java5才被引入,其做法就是:把 system jar 文件打包成為"shared archive",這些"shared archive"會(huì)作為memory-mapped in文件存在,共享于不同的JVM 進(jìn)程間,以減少JVMfootprint,加快Java應(yīng)用的啟動(dòng)時(shí)間。

            值得一提的是:兩者都有所謂的HELL問題(JAR HELL vs DLL HELL),新老版本的兼容問題始終讓人頭疼。

詳見解釋:

http://en.wikipedia.org/wiki/DLL_hell

http://en.wikipedia.org/wiki/JAR_hell#JAR_hell

    


posted @ 2009-02-18 15:28 愛上龍卷風(fēng) 閱讀(2596) | 評(píng)論 (2)編輯 收藏

2009年1月12日 #

題目是這樣的:
問:不使用任何循環(huán)語句,遞歸,輸出打印n條(n>1) "Hello World"。
解這道題目,利用了c++語言一個(gè)非常重要的特性:
c++允許定義基于statck數(shù)據(jù)區(qū)的Object。由此,不由想到了Java.
在Java的世界里,所有的類型都是引用(或者稱為指針), 對(duì)象內(nèi)存的分配都是通過new從heap上顯式的分配,無法在Java里構(gòu)建基于statck數(shù)據(jù)區(qū)的對(duì)象。所以在Java里,這道題目是無解的。
Java之于c++,既是進(jìn)步,又是倒退。
Java語言本身的確幫助c++程序員做了很多事情,比如GC, 去掉了c++中很多復(fù)雜的特性,比如多重繼承,運(yùn)算符重載等。
同時(shí),c++本身的很多優(yōu)點(diǎn),也喪失了。 寫了c++, 然后再寫java,一個(gè)明顯的感覺是,沒法使用java寫出像c++一樣簡潔的程序。
比如, 沒有了運(yùn)算符重載, 你不得不使用equal方法來表達(dá)兩個(gè)對(duì)象的相等。
Java不能顯式表達(dá)RAII概念,你不得不使用hard code的方法Log方法的進(jìn)入和退出, 如:
func() {
log("enter func");
//do something.
log("exit func");
}

posted @ 2009-01-12 22:28 愛上龍卷風(fēng) 閱讀(2846) | 評(píng)論 (23)編輯 收藏

2009年1月3日 #

我在IBM dwWorks上發(fā)了一篇關(guān)于Monitor Object 并發(fā)設(shè)計(jì)模式的文章。

Monitor Object 并發(fā)模式在 Java 同步機(jī)制中的實(shí)現(xiàn)。 里面用了很多C++的設(shè)計(jì)行為,來討論Java的Monitor Object.
感興趣的話,歡迎訪問
以下是摘要:

文章將從兩個(gè)方面進(jìn)行闡述:

  1. 使用 C++ 語言來描述 Monitor Object 設(shè)計(jì)模式。Java 對(duì)于這樣一個(gè)典型的模式做了很好的語言層面的封裝,因此對(duì)于 Java 的開發(fā)者來說,很多關(guān)于該模式本身的東西被屏蔽掉了。本文試圖使用 Native C++ 語言,幫助讀者從本質(zhì)上對(duì) Monitor object 設(shè)計(jì)模式有一個(gè)更全面的認(rèn)識(shí)。
  2. 結(jié)合 C++ 版本的 Monitor Object 設(shè)計(jì)模式,引領(lǐng)讀者對(duì)于 Java 同步機(jī)制有一個(gè)更深刻的認(rèn)識(shí),幫助讀者正確有效地使用 Java 同步機(jī)制。


posted @ 2009-01-03 18:26 愛上龍卷風(fēng) 閱讀(2547) | 評(píng)論 (2)編輯 收藏

2008年2月3日 #

        一般來講, 在服務(wù)器上,如果有足夠的資源,Winsock server,理論上可以支持成千的并發(fā)連接。而現(xiàn)實(shí)是,我們沒有足夠的資源可供使用,分配。本文主要來討論一下內(nèi)存資源之于Winsock server開發(fā)的重要性。
一)基本概念。
-> Pages,Locked Pages.
        在現(xiàn)代操作系統(tǒng)中,內(nèi)存管理會(huì)把主存(RAM)分成Pages來管理。 Paging(或者swapping)指的是主存與外存之間以Page為單位進(jìn)行數(shù)據(jù)的交換。Locked Pages指的是被鎖定在主存中的內(nèi)存頁,以保證一些內(nèi)核組件,driver可以訪問到它們。windows一定會(huì)保證一定數(shù)量的可交換的內(nèi)存空間,防止一些非法程序鎖定所有的物理內(nèi)存,而致使系統(tǒng)崩潰。在windows NT, windows 2000上,可鎖定的內(nèi)存總的大小上限大概是物理內(nèi)存的1/8(當(dāng)然對(duì)于程序的開發(fā)人員,不應(yīng)該對(duì)這個(gè)值進(jìn)行任何的假設(shè),這個(gè)值可能會(huì)隨著操作系統(tǒng)本版的變化而變化)。在Winsock應(yīng)用開發(fā)過程中,以overlapped方式讀寫IO操作,將會(huì)導(dǎo)致內(nèi)存被鎖定。
-> working set
        在程序開始運(yùn)行,并達(dá)到其穩(wěn)定的運(yùn)行狀態(tài)(主要指的是其對(duì)內(nèi)存的使用),在這個(gè)狀態(tài)下,程序使用內(nèi)存的數(shù)量一般小于其需要使用內(nèi)存的總量。這樣一個(gè)穩(wěn)定的運(yùn)行狀態(tài),我們可以稱為working set: 被該程序頻繁訪問的內(nèi)存頁的集合。在windows上,你可以使用SetWorkingSetSize Win32 API來增加程序使用物理內(nèi)存的數(shù)量。
-> non-paged pool
       不可交換的內(nèi)存。這主要指以non-paged的方式分配的內(nèi)存,這些內(nèi)存就像locked pages一樣,是從來不會(huì)被交換出去的,用來存放一些由內(nèi)核組件,driver訪問的信息。 在Winsock應(yīng)用開發(fā)過程中,以下的操作可能導(dǎo)致分配non-paged內(nèi)存。
1) 調(diào)用系統(tǒng)一些系統(tǒng)的API,例如打開文件,create socket,等,都會(huì)導(dǎo)致從non-paged pool分配內(nèi)存。
2) 一些driver可以顯式地從該區(qū)域分配內(nèi)存。
二) Winsock server上Locked Pages使用。
        我們提到過,任何的overlapped IO操作,都會(huì)導(dǎo)致鎖定內(nèi)存頁。這些內(nèi)存頁一旦被locked,就不會(huì)被交換出去。我們知道,windows操作系統(tǒng)對(duì)最大的可鎖定內(nèi)存頁做了一個(gè)上限,如果超出這個(gè)上限,overlapped IO調(diào)用將會(huì)導(dǎo)致WSAENOBUFS錯(cuò)誤。
        考慮下面的情況,如果server在每個(gè)連接上會(huì)發(fā)出很多的overlapped receives操作,那么,隨著連接數(shù)目的增多,很明顯,被鎖定的內(nèi)存數(shù)量很有可能達(dá)到上限而導(dǎo)致WSAENOBUFS錯(cuò)誤。在這種情況下,如果服務(wù)器預(yù)期會(huì)處理大數(shù)量的客戶端連接,則需要服務(wù)器在每個(gè)連接上發(fā)出zero-byte buffer的overlapped接收請(qǐng)求(這種情況下,因?yàn)閠he size of buffer is zero,所以沒有任何內(nèi)存被鎖定),一旦overlapped接收操作完成,server可以以non-blocking方式執(zhí)行receive操作,以取得所有緩存在so_rcvbuf中的數(shù)據(jù),直到返回WSAEWOULDBLOCK為止。
        另外需要注意的是,windows在page的邊界上對(duì)內(nèi)存進(jìn)行鎖定,在x86平臺(tái)上,它是4kb的整數(shù)倍。所以,假如你post了一個(gè)1 KB buffer,而系統(tǒng)真實(shí)鎖定的是4 KB 的大小,為了避免這樣的浪費(fèi),盡量用4kb的整數(shù)做overlapped  IO操作。
三) Winsock server上non-paged pool使用。
        同Locked Pages限制一樣,windows對(duì)non-paged pool也有一個(gè)最大的限制。并且,當(dāng)你的應(yīng)用出現(xiàn)這個(gè)問題的時(shí)候,超出它的最大限制數(shù),情況要遠(yuǎn)比Locked Pages復(fù)雜。這種情況下,后果是不確定的,有可能你的Winsock調(diào)用返回WSAENOBUFS錯(cuò)誤,也有可能,在系統(tǒng)中,一個(gè)和你的應(yīng)用毫無關(guān)聯(lián)的driver由于申請(qǐng)不到non-paged內(nèi)存而致使system crash。而這樣的災(zāi)難,是沒法恢復(fù)的。
        考慮一個(gè)具體的例子:我們假設(shè)在windows2000上,系統(tǒng)有1 GB內(nèi)存。這樣的配置下,windows大概會(huì)預(yù)留1/4的空間用作non-paged pool(同樣,對(duì)于程序的開發(fā)人員,不應(yīng)該對(duì)這個(gè)值進(jìn)行任何的假設(shè)),即:256MB。這樣的配置下,保守估計(jì),我們的Winsock server能夠處理到大概50,000連接,或者更多。(每個(gè)accepted socket大概消耗1.5kb,每個(gè)連接上post一個(gè)overlapped操作,分配一個(gè)IRP,大概需要500 byte, 總計(jì):(1500+500)*50,000 = 100 Mb) 。
       無論是對(duì)于Locked Pages,還是對(duì)于non-paged pool使用,一旦超出了上限,Winsock調(diào)用僅僅會(huì)返回一般的WSAENOBUFS 或者ERROR_INSUFFICIENT_RESOURCES錯(cuò)誤。為了處理這些錯(cuò)誤,你可以試試以下的方法:
1) 需要首先調(diào)用SetWorkingSetSize,增加應(yīng)用的可支配資源數(shù),看能否解決。
2)     確信你的應(yīng)用沒有做出太多的overlapped  IO操作。
3) 關(guān)閉一些連接數(shù)。
四) SOCKET的緩沖區(qū)設(shè)置問題。

         Winsock在默認(rèn)的情況下,每個(gè)socket都會(huì)與一個(gè)send和receive buffer相關(guān)聯(lián)。你可以通過調(diào)用setsockopt來設(shè)置buffer的大小。
        在緩沖區(qū)沒有被關(guān)閉的情況下,我們看看overlapped send和revc是怎么工作的。
        當(dāng)上層的應(yīng)用做出了send調(diào)用,而這時(shí)如果send buffer還有剩余的空間,那么數(shù)據(jù)將會(huì)從用戶提交的buffer復(fù)制到send buffer中,然后調(diào)用返回成功。否則,假如這時(shí)send buffer已滿,用戶提交的buffer將會(huì)被鎖定,并且調(diào)用返回WSA_IO_PENDING。當(dāng)send buffer的數(shù)據(jù)被下層的tcp處理完成,winsock將直接處理用戶提交的buffer里的數(shù)據(jù),而不需要再復(fù)制。
        同樣,對(duì)于recv操作,如果數(shù)據(jù)已經(jīng)被緩存在socket的receive buffer里,當(dāng)發(fā)生recv調(diào)用的時(shí)候,數(shù)據(jù)將直接從socket的receive buffer復(fù)制到用戶的buffer里,recv調(diào)用返回成功。否則,假如發(fā)生調(diào)用時(shí)receive buffer里沒有數(shù)據(jù),用戶提交的buffer將會(huì)被鎖定,recv調(diào)用返回WSA_IO_PENDING。當(dāng)數(shù)據(jù)到達(dá)當(dāng)前連接,將會(huì)被直接復(fù)制到用戶提交的buffer里。
        一個(gè)應(yīng)用程序通過設(shè)定send buffer為0,把緩沖區(qū)關(guān)閉,然后發(fā)出一個(gè)阻塞send()調(diào)用。在這樣的情況下,系統(tǒng)內(nèi)核會(huì)把應(yīng)用程序的緩沖區(qū)鎖定,直到接收方確認(rèn)收到了整個(gè)緩沖區(qū)后send()調(diào)用才返回。似乎這是一種判定你的數(shù)據(jù)是否已經(jīng)為對(duì)方全部收到的簡潔的方法,實(shí)際上卻并非如此。想想看,即使遠(yuǎn)端tcp通知數(shù)據(jù)已經(jīng)收到,其實(shí)也根本不代表數(shù)據(jù)已經(jīng)成功送給客戶端應(yīng)用程序,比如對(duì)方可能發(fā)生資源不足的情況,導(dǎo)致afd.sys不能把數(shù)據(jù)拷貝給應(yīng)用程序。另一個(gè)更要緊的問題是,在每個(gè)線程中每次只能進(jìn)行一次發(fā)送調(diào)用,效率極其低下。
        另外,希望通過關(guān)閉Winsock緩沖區(qū),從而避免數(shù)據(jù)復(fù)制,達(dá)到優(yōu)化性能的目的,也是不可取的。從上面,我們看到:只要應(yīng)用保證適量的,足夠的send, recv調(diào)用,這樣的復(fù)制是完全可以避免的。
        高性能的服務(wù)器應(yīng)用程序可以關(guān)閉發(fā)送緩沖區(qū),同時(shí)不會(huì)損失性能。不過,這樣的應(yīng)用程序必須十分小心,保證它總是發(fā)出多個(gè)重疊發(fā)送調(diào)用,而不是等待某個(gè)重疊發(fā)送結(jié)束了才發(fā)出下一個(gè)。如果應(yīng)用程序是按一個(gè)發(fā)完再發(fā)下一個(gè)的順序來操作,那浪費(fèi)掉兩次發(fā)送中間的空檔時(shí)間,總之是要保證傳輸驅(qū)動(dòng)程序在發(fā)送完一個(gè)緩沖區(qū)后,立刻可以轉(zhuǎn)向另一個(gè)緩沖區(qū)。
        如果關(guān)閉了recv buffer,在你的應(yīng)用沒有保證足夠的recv操作前提下,任何進(jìn)來數(shù)據(jù),必須在TCP層進(jìn)行緩存,最大緩存的數(shù)量將取決于tcp windows的大小(17Kb)。而最為嚴(yán)重的是這些緩存是從non-paged pool分配而來。如上所述,non-paged pool是非常珍貴,稀缺的內(nèi)存。所以,從這個(gè)意義上來講,關(guān)閉了recv buffer操作是不可取的。
    這是2007年最后一篇帖子,最后祝大家新年快樂,過個(gè)好年!!!

posted @ 2008-02-03 15:18 愛上龍卷風(fēng) 閱讀(3117) | 評(píng)論 (2)編輯 收藏

2008年1月5日 #

四、c++中的多態(tài)規(guī)則。
一) c++中函數(shù)動(dòng)態(tài)綁定規(guī)則。
看下面的例子:

class Window
{

public:
  virtual 
void  oops()
  
{
    cout
<<"Window oops"<<endl;
  }

public:
  
int height;
  
int width;
}
;
class TextWindow : public Window
{

public:
  virtual 
void  oops()
  
{
    cout
<<"TextWindow oops"<<cursorLocation<<endl;
  }

public:
  
int cursorLocation;
}
;

main()
{
  Window win;
  Window
* tWin;
  TextWindow 
* tWinPtr;

  tWinPtr 
= new TextWindow;
  tWin 
= tWinPtr;
  
  win.oops();
  tWin
->oops();
}


類TextWindow繼承與類Window。我想程序運(yùn)行的結(jié)果,大多數(shù)熟悉C++的人都會(huì)知道,
win.oops()最終調(diào)用的是父類oops函數(shù),而tWin->oops()調(diào)用的是子類TextWindow的函數(shù)。
通過這個(gè)例子,我們先總結(jié)一下c++中的多態(tài)調(diào)用規(guī)則
第一、對(duì)于指針和引用類型,當(dāng)消息調(diào)用的成員函數(shù)有可能被重寫時(shí),最終被選擇調(diào)用的成員函數(shù)由消息接收者的動(dòng)態(tài)類型確定(注意:在OO概念中,對(duì)某個(gè)對(duì)象成員函數(shù)進(jìn)行調(diào)用,常常稱為給該對(duì)象發(fā)送消息,該對(duì)象就是消息的接收者)。
      如上例:tWin->oops(),由于tWin的動(dòng)態(tài)類型是子類TextWindow,而不是Windows,所以tWin->oops()調(diào)用的是子類TextWindow的函數(shù)。
二、對(duì)于其它的變量,對(duì)虛擬函數(shù)調(diào)用綁定完全由該變量的靜態(tài)類型確定(即該變量的聲明),而不是該變量的真實(shí)類型確定。
     如上例:win.oops(),由于win的聲明類型為Window類,所以其結(jié)果調(diào)用的是父類oops函數(shù)。
二) 探討。
     接下來,我們要看的問題是,在c++中,為什么對(duì)于多態(tài)規(guī)則(或者說是動(dòng)態(tài)函數(shù)綁定規(guī)則),做出了兩中不同的劃分,即:只有指針與引用類型,才進(jìn)行函數(shù)的后期動(dòng)態(tài)綁定,也就是多態(tài)。這或許也是許多c++初學(xué)者非常迷惑的地方。這種規(guī)則的不一致性,的確給c++的語法造成一定的復(fù)雜性。而這在Java,或者C#中是沒有的,后面我們會(huì)涉及到。
      我們先來看例子。 

void f()
{
  Window  win;
  Window
* tWinPtr;
  
  tWinPtr 
= new TextWindow;
  win     
= *tWinPtr;//what's problem happen
   
  win.oops(); 
//what's problem happen
  tWinPtr->oops();
}


      在這里,如果我們假設(shè),c++的函數(shù)動(dòng)態(tài)綁定規(guī)則是一致的,看看會(huì)發(fā)生什么問題???
      現(xiàn)在win被聲明為Window類型,然而其真實(shí)的類型為TextWindow(因?yàn)閣in=*tWinPtr),由于我們的假設(shè),win現(xiàn)在是允許進(jìn)行動(dòng)態(tài)函數(shù)綁定的,所以當(dāng)執(zhí)行win.oops()時(shí),實(shí)際上是調(diào)用子類TextWindow的成員函數(shù)。
     現(xiàn)在,我們有必要來審視一下win變量的內(nèi)存布局。由于win變量是在棧上聲明的變量,其內(nèi)存也是從棧進(jìn)行分配(這是c++從c語言那里繼承過來的優(yōu)良特質(zhì),從棧上分配內(nèi)存空間比動(dòng)態(tài)分配內(nèi)存有更好的執(zhí)行速度),c++標(biāo)準(zhǔn)規(guī)定:給win變量分配內(nèi)存空間的大小,由其靜態(tài)的類型確定,即應(yīng)該是Window類所使用的內(nèi)存空間大小。在這種情況下,當(dāng)執(zhí)行win=*tWinPtr時(shí),什么會(huì)發(fā)生?如下圖:

在默認(rèn)的拷貝構(gòu)造函數(shù)情況下,信息會(huì)出現(xiàn)丟失,這就是著名的slicing off現(xiàn)象。結(jié)果,變量cursorLocation在win的內(nèi)存空間里丟失了。然而,問題是:在我們假設(shè)下,我們要求win.oops()導(dǎo)致TextWindow的成員函數(shù)調(diào)用,而在這個(gè)函數(shù)中,訪問到的cursorLocation變量是不存在!win.oops()調(diào)用將導(dǎo)致內(nèi)存違例!
      到這里,我們可以總結(jié)一下:c++標(biāo)準(zhǔn)基于的其特定的內(nèi)存分配規(guī)則,給出了以上,我們?cè)谇耙还?jié)總結(jié)出的函數(shù)動(dòng)態(tài)綁定規(guī)則。
三) 深入。
      當(dāng)然,我們也可以說,c++也可以通過改變其內(nèi)存分配規(guī)則,來給出一個(gè)一致性的函數(shù)動(dòng)態(tài)綁定規(guī)則。比如:可以考慮在給win變量分配內(nèi)存空間時(shí),考慮其所有子類需求,然后分配最大數(shù)量的內(nèi)存空間給win變量。這種做法可行性很差,對(duì)于編譯器而言,需要掃描整個(gè)程序(確定該類的所有子類),才能確定最大的內(nèi)存空間是多少。在使用類庫或者框架的情況下,會(huì)引起整個(gè)類庫,框架的重新編譯,這是得不償失的!而這種做法,在oo的語言中,基本上是沒有的。這也是c++不得不基于其現(xiàn)有的內(nèi)存管理機(jī)制,而對(duì)多態(tài)規(guī)則作出的不一致的解釋。
    對(duì)于這個(gè)c++現(xiàn)有的內(nèi)存管理機(jī)制,我們?nèi)绻麖牧硗饨嵌热ダ斫獾脑?是很合理的。當(dāng)win=*tWinPtr發(fā)生
時(shí),我們可以類似地認(rèn)為:好比一個(gè)float類型的數(shù)賦給了一個(gè)interger類型的變量,其結(jié)果當(dāng)然是float的值被截?cái)嗔恕?br>    我們?cè)賮砜雌渌Z言,Java(或者C#)是怎么解決的。
    最重要的一點(diǎn)是,在Java(C#)中只有引用的概念,所以在棧上聲明的類的變量,只需要分配一個(gè)指針大小的內(nèi)存空間就行了,而不需要給該變量分配空間來保存變量內(nèi)容本身,這其實(shí)就是我們現(xiàn)在看到的c++中指針和引用的情況。
    

 

posted @ 2008-01-05 23:12 愛上龍卷風(fēng) 閱讀(3197) | 評(píng)論 (8)編輯 收藏

2007年11月11日 #

        C++之父Bjarne stroustrup曾經(jīng)說過:不需要了解所有的c++細(xì)節(jié),也能夠?qū)懗龊玫腸++程序;不應(yīng)該注重語言方面的特征,而應(yīng)該注重軟件設(shè)計(jì)技術(shù)本身。很顯然,我的這篇文章,與這兩句話背道而馳:).的確,我們程序員,不應(yīng)該把精力放在c++本身語言的特征上,而是應(yīng)該思考軟件設(shè)計(jì)技術(shù)本身。那么,在我們需要提高對(duì)c++理解的同時(shí),是不是我們從下面幾個(gè)方面為著眼點(diǎn)
1) 從編譯原理的角度
2) 從技術(shù)需求的角度
3) 從軟件設(shè)計(jì)技術(shù)的角度
從以上的幾個(gè)角度,來重新審視c++一些晦澀語法,或許,我們能從中獲益。在這里,我要說的是,我們不單單是要記住這些c++語言特性怎么樣的使用,而是應(yīng)該知道這些語言特性背后隱藏的故事,以便于我們更深層次地理解c++,理解軟件設(shè)計(jì)。
一、子類通過函數(shù)名字隱藏父類函數(shù)。
如下例:

class Base
{
public:
 virtual 
void f(int x);
}
;
class Derived: public Base
{
public:
 virtual 
void f(double* pd);
}
;
int main()
{
  Derived
* pd = new Derived();
  pd
->f(10); //compile error!!!
}

         當(dāng)我們編譯pd->f(10)操作時(shí),編譯器報(bào)錯(cuò)。按照我們常規(guī)的理解是:父類的函數(shù)void f(int x)與子類的函數(shù)void f(double*pd),由于參數(shù)類型不同,其函數(shù)簽名也是不一樣的,按照這樣的邏輯,在這個(gè)類繼承體系中,這兩個(gè)函數(shù)完全應(yīng)該是互不隱藏的,我們完全可以認(rèn)為是符合overloaded規(guī)則的兩個(gè)函數(shù)。
        但是,在c++里,子類通過函數(shù)名字隱藏父類函數(shù),而不是通過函數(shù)簽名!c++給出的解釋也是合理的:試想一種情況:你使用了別人寫的類庫,繼承其中的某個(gè)類,寫了你自己的子類。
如上面的例子,你的子類就是Derived,而類庫中的父類就是Base.當(dāng)你根本不知道在父類中還有這樣一個(gè)f(int x)函數(shù)時(shí),在調(diào)用子類Derived的f函數(shù)時(shí),你犯了錯(cuò)誤,參數(shù)類型傳成了int類型(或者不是你犯的錯(cuò)誤,編譯器幫你自動(dòng)轉(zhuǎn)化為int類型),結(jié)果是:程序可以正常運(yùn)行,但是,執(zhí)行的結(jié)果卻不是你所期望的,是f(int x)調(diào)用,而不是你自己的實(shí)現(xiàn):f(double* pd)調(diào)用!
         這就是c++為什么通過函數(shù)名字隱藏父類函數(shù)的原因。
        說到這里,我們需要補(bǔ)充幾句:雖然c++在語言層面上給我們提供了這樣的保證,但是,子類hide父類的函數(shù),這是一個(gè)非常不好的設(shè)計(jì)。從OO的角度出發(fā),應(yīng)該講求的是Liskov Substitution Principle。即:suntypes must be substitutable fro their base types.很顯然,當(dāng)hide行為發(fā)生時(shí),從接口的角度來講,子類與父類是不能互為替代的。父類的protected or public的方法,應(yīng)該很自然地由其所有子類所繼承,而不是被隱藏。隱藏行為的發(fā)生,相當(dāng)于在這套繼承體系中開的一個(gè)后門。很顯然,C++幫助我們自動(dòng)隱藏了父類的方法,但是,作為程序開發(fā)的我們,應(yīng)該意識(shí)到這一點(diǎn),也應(yīng)該避免這樣的設(shè)計(jì)。
二、c++的per-class allocator語法規(guī)則
          在D&E of C++一書中,Stroustrup給出了幾點(diǎn)c++提供per-class allocator的理由,這些理由也是我們使用class level的allocator的原因,所以,有必要我們總結(jié)一下:
第一、許多程序應(yīng)用,需要在運(yùn)行的過程中,大量地Create和Delete對(duì)象。這些對(duì)象,諸如:tree nodes,linked list nodes,messages等等。如果在傳統(tǒng)的heap完成這些對(duì)象的創(chuàng)建,銷毀,由于大量的內(nèi)存申請(qǐng),釋放,勢必會(huì)造成內(nèi)存碎片。這種情況下,我們需要對(duì)內(nèi)存分配進(jìn)行細(xì)粒度的控制。
第二、一些應(yīng)用需要長時(shí)間跑在內(nèi)存受限的裝置上,這也需要我們對(duì)內(nèi)存分配進(jìn)行細(xì)粒度的控制,而不是無限制地分配,釋放。
主要基于以上的兩點(diǎn),c++提供了per-class allocator語言支持。
如下例:

class X
{
public:
  
void* operator new(size_t sz); //allocate sz bytes
  void  operator delete(void* p) //free p;
}
;

      new操作符函數(shù)負(fù)責(zé)對(duì)象X的內(nèi)存分配。對(duì)這樣一個(gè)語法規(guī)則,我們好奇的是,為什么聲明了一個(gè)我們從來都不使用的參數(shù)size_t sz.我們的使用語法如下: X* px = new X;
C++也給出了解釋:per-class allocator機(jī)制將適用整個(gè)類的繼承體系。例如:

class Y: public X //ojects of class Y are also allocated using X::operator new
{
  
//
  
// 
}
;

        對(duì)于子類Y,其內(nèi)存分配函數(shù)也是X::operator new()。但是,在這里,內(nèi)存分配的大小,不應(yīng)該是sizeof(X),而是sizeof(Y).問題的關(guān)鍵在這里:C++通過提供多余的參數(shù)size_t sz,而給開發(fā)者提供了更大的靈活性,也即:per-class allocator是面向類的繼承體系的內(nèi)存管理機(jī)制,而不單單是面向單個(gè)類。
三、Koenig Lookup機(jī)制。
        大家對(duì)Andrew Koenig應(yīng)該很熟悉,c++大牛,是AT&T公司Shannon實(shí)驗(yàn)室大規(guī)模編程研究部門中的成員,同時(shí)他也是C++標(biāo)準(zhǔn)委員會(huì)的項(xiàng)目編輯。他擁有超過30年的編程經(jīng)驗(yàn),其中有15年的C++使用經(jīng)驗(yàn)。
        Koenig Lookup,就是以Andrew Koenig命名的查找規(guī)則。在看這個(gè)定義之前,我們先弄清楚函數(shù)所在的域的分類,一般來講,分為:
1:類域(函數(shù)作為某個(gè)類的成員函數(shù)(靜態(tài)或非靜態(tài)))
2:名字空間域
3:全局域(即C++默認(rèn)的namespace)
        而Koenig Lookup機(jī)制,就是當(dāng)編譯器對(duì)無限定域的函數(shù)調(diào)用進(jìn)行名字查找時(shí),除了當(dāng)前名字空間域以外,也會(huì)把函數(shù)參數(shù)類型所處的名字空間加入查找的范圍。
如下例:

#include <iostream>
using namespace std;
namespace Koenig
{
    
class MyArg
    
{
    
public:
         ostream
& print(ostream& out) const
         
{
            out
<<"this is MyArg."<<endl;
         }

    }
;
 
    inline ostream
& operator<<(ostream& out, const MyArg& myArg)
    
{
         
return myArg.print(out);
    }

}

 
int main()
{
    Koenig::MyArg myArg;
    cout
<<myArg;
    
return 0;
}

       如上的代碼,使用operator<<操作符函數(shù),打印對(duì)象的狀態(tài),但是函數(shù)ostream& operator<<(ostream& out, const MyArg& myArg) 的定義域是處于名字空間Koenig中,為什么編譯器在解析main函數(shù)(全局域)里面的operator<<調(diào)用時(shí),它能夠正確定位到Koenig名字空間里面的operator<<?這是因?yàn)楦鶕?jù)Koenig查找規(guī)則,編譯器需要把參數(shù)類型MyArg所在的名字空間Koenig也加入對(duì)ostream& operator<<(ostream& out, const MyArg& myArg) 調(diào)用的名字查找范圍中。
       
如果沒有Koenig查找規(guī)則,我們就無法直接寫cout<<myArg;,而是需要寫類似Koenig::operator<<(std::cout, myArg); 這樣的代碼(使用完全限定名)。這樣的結(jié)果是,即不直觀也不方便。

        其實(shí)在C++里,提供了很多類似于Koenig查找規(guī)則的機(jī)制,以保證程序語法上的簡潔,明了。例如:許多的操作符函數(shù),COPY構(gòu)造函數(shù)。而這些,也是我們寫出專業(yè)的C++程序的基本。
未完待續(xù):)

 

 


 

posted @ 2007-11-11 14:56 愛上龍卷風(fēng) 閱讀(4675) | 評(píng)論 (26)編輯 收藏

2007年8月26日 #

         本文主要探討一下windows平臺(tái)上的完成端口開發(fā)及其與之相關(guān)的幾個(gè)重要的技術(shù)概念,這些概念都是與基于IOCP的開發(fā)密切相關(guān)的,對(duì)開發(fā)人員來講,又不得不給予足夠重視的幾個(gè)概念:
1) 基于IOCP實(shí)現(xiàn)的服務(wù)吞吐量
2)IOCP模式下的線程切換
3)基于IOCP實(shí)現(xiàn)的消息的亂序問題。

一、IOCP簡介
    提到IOCP,大家都非常熟悉,其基本的編程模式,我就不在這里展開了。在這里我主要是把IOCP中所提及的概念做一個(gè)基本性的總結(jié)。IOCP的基本架構(gòu)圖如下:
 

如圖所示:在IOCP中,主要有以下的參與者:
--》完成端口:是一個(gè)FIFO隊(duì)列,操作系統(tǒng)的IO子系統(tǒng)在IO操作完成后,會(huì)把相應(yīng)的IO packet放入該隊(duì)列。
--》等待者線程隊(duì)列:通過調(diào)用GetQueuedCompletionStatus API,在完成端口上等待取下一個(gè)IO packet。
--》執(zhí)行者線程組:已經(jīng)從完成端口上獲得IO packet,在占用CPU進(jìn)行處理。
除了以上三種類型的參與者。我們還應(yīng)該注意兩個(gè)關(guān)聯(lián)關(guān)系,即:
--》IO Handle與完成端口相關(guān)聯(lián):任何期望使用IOCP的方式來處理IO請(qǐng)求的,必須將相應(yīng)的IO Handle與該完成端口相關(guān)聯(lián)。需要指出的時(shí),這里的IO Handle,可以是File的Handle,或者是Socket的Handle。
--》線程與完成端口相關(guān)聯(lián):任何調(diào)用GetQueuedCompletionStatus API的線程,都將與該完成端口相關(guān)聯(lián)。在任何給定的時(shí)候,該線程只能與一個(gè)完成端口相關(guān)聯(lián),與最后一次調(diào)用的GetQueuedCompletionStatus為準(zhǔn)。
二、高并發(fā)的服務(wù)器(基于socket)實(shí)現(xiàn)方法
        一般來講,實(shí)現(xiàn)基于socket的服務(wù)器,有三種實(shí)現(xiàn)的方式(thread per request的方式,我就不提了:)):
第一、線程池的方式。使用線程池來對(duì)客戶端請(qǐng)求進(jìn)行服務(wù)。使用這種方式時(shí),當(dāng)客戶端對(duì)服務(wù)器的連接是短連接(所謂的短連接,即:客戶端對(duì)服務(wù)器不是長時(shí)間連接)時(shí),是可以考慮的。但是,如若客戶端對(duì)服務(wù)器的連接是長連接時(shí),我們需要限制服務(wù)器端的最大連接數(shù)目為線程池線程的最大數(shù)目,而這應(yīng)用的設(shè)計(jì)本身來講,是不好的設(shè)計(jì)方式,scalability會(huì)存在問題。
第二、基于Select的服務(wù)器實(shí)現(xiàn)。其本質(zhì)是,使用Select(操作系統(tǒng)提供的API)來監(jiān)視連接是否可讀,可寫,或者是否出錯(cuò)。相比于前一種方式,Select允許應(yīng)用使用一個(gè)線程(或者是有限幾個(gè)線程)來監(jiān)視連接的可讀寫性。當(dāng)有連接可讀可寫時(shí),應(yīng)用可以以non-bolock的方式讀寫socket上的數(shù)據(jù)。使用Select的方式的缺點(diǎn)是,當(dāng)Select所監(jiān)視的連接數(shù)目在千的數(shù)量級(jí)時(shí),性能會(huì)打折扣。這是因?yàn)椴僮飨到y(tǒng)內(nèi)核需要在內(nèi)部對(duì)這些Socket進(jìn)行輪詢,以檢查其可讀寫性。另一個(gè)問題是:應(yīng)用必須在處理完所有的可讀寫socket的IO請(qǐng)求之后,才能再次調(diào)用Select,進(jìn)行下一輪的檢查,否則會(huì)有潛在的問題。這樣,造成的結(jié)果是,對(duì)一些請(qǐng)求的處理會(huì)出現(xiàn)饑餓的現(xiàn)象。
        一般common的做法是Select結(jié)合Leader-Follower設(shè)計(jì)模式使用。不過不管怎樣,Select的本質(zhì)造成了其在Scalability的問題是不如IOCP,這也是很多high-scalabe的服務(wù)器采用IOCP的原因。
第三、IOCP實(shí)現(xiàn)高并發(fā)的服務(wù)器。IOCP是實(shí)現(xiàn)high-scalabe的服務(wù)器的首選。其特點(diǎn)我們專門在下一小姐陳述。
三、IOCP開發(fā)的幾個(gè)概念
第一、服務(wù)器的吞吐量問題。
      我們都知道,基于IOCP的開發(fā)是異步IO的,也正是這一技術(shù)的本質(zhì),決定了IOCP所實(shí)現(xiàn)的服務(wù)器的高吞吐量。
       我們舉一個(gè)及其簡化的例子,來說明這一問題。在網(wǎng)絡(luò)服務(wù)器的開發(fā)過程中,影響其性能吞吐量的,有很多因素,在這里,我們只是把關(guān)注點(diǎn)放在兩個(gè)方面,即:網(wǎng)絡(luò)IO速度與Disk IO速度。我們假設(shè):在一個(gè)千兆的網(wǎng)絡(luò)環(huán)境下,我們的網(wǎng)絡(luò)傳輸速度的極限是大概125M/s,而Disk IO的速度是10M/s。在這樣的前提下,慢速的Disk 設(shè)備會(huì)成為我們整個(gè)應(yīng)用的瓶頸。我們假設(shè)線程A負(fù)責(zé)從網(wǎng)絡(luò)上讀取數(shù)據(jù),然后將這些數(shù)據(jù)寫入Disk。如果對(duì)Disk的寫入是同步的,那么線程A在等待寫完Disk的過程是不能再從網(wǎng)絡(luò)上接受數(shù)據(jù)的,在寫入Disk的時(shí)間內(nèi),我們可以認(rèn)為這時(shí)候Server的吞吐量為0(沒有接受新的客戶端請(qǐng)求)。對(duì)于這樣的同步讀寫Disk,一些的解決方案是通過增加線程數(shù)來增加服務(wù)器處理的吞吐量,即:當(dāng)線程A從網(wǎng)絡(luò)上接受數(shù)據(jù)后,驅(qū)動(dòng)另外單獨(dú)的線程來完成讀寫Disk任務(wù)。這樣的方案缺點(diǎn)是:需要線程間的合作,需要線程間的切換(這是另一個(gè)我們要討論的問題)。而IOCP的異步IO本質(zhì),就是通過操作系統(tǒng)內(nèi)核的支持,允許線程A以非阻塞的方式向IO子系統(tǒng)投遞IO請(qǐng)求,而后馬上從網(wǎng)絡(luò)上讀取下一個(gè)客戶端請(qǐng)求。這樣,結(jié)果是:在不增加線程數(shù)的情況下,IOCP大大增加了服務(wù)器的吞吐量。說到這里,聽起來感覺很像是DMA。的確,許多軟件的實(shí)現(xiàn)技術(shù),在本質(zhì)上,與硬件的實(shí)現(xiàn)技術(shù)是相通的。另外一個(gè)典型的例子是硬件的流水線技術(shù),同樣,在軟件領(lǐng)域,也有很著名的應(yīng)用。好像話題扯遠(yuǎn)了,呵呵:)
第二、線程間的切換問題。
         服務(wù)器的實(shí)現(xiàn),通過引入IOCP,會(huì)大大減少Thread切換帶來的額外開銷。我們都知道,對(duì)于服務(wù)器性能的一個(gè)重要的評(píng)估指標(biāo)就是:System\Context Switches,即單位時(shí)間內(nèi)線程的切換次數(shù)。如果在每秒內(nèi),線程的切換次數(shù)在千的數(shù)量級(jí)上,這就意味著你的服務(wù)器性能值得商榷。Context Switches/s應(yīng)該越小越好。說到這里,我們來重新審視一下IOCP。
     完成端口的線程并發(fā)量可以在創(chuàng)建該完成端口時(shí)指定(即NumberOfConcurrentThreads參數(shù))。該并發(fā)量限制了與該完成端口相關(guān)聯(lián)的可運(yùn)行線程的數(shù)目(就是前面我在IOCP簡介中提到的執(zhí)行者線程組的最大數(shù)目)。當(dāng)與該完成端口相關(guān)聯(lián)的可運(yùn)行線程的總數(shù)目達(dá)到了該并發(fā)量,系統(tǒng)就會(huì)阻塞任何與該完成端口相關(guān)聯(lián)的后續(xù)線程的執(zhí)行,直到與該完成端口相關(guān)聯(lián)的可運(yùn)行線程數(shù)目下降到小于該并發(fā)量為止。最有效的假想是發(fā)生在有完成包在隊(duì)列中等待,而沒有等待被滿足,因?yàn)榇藭r(shí)完成端口達(dá)到了其并發(fā)量的極限。此時(shí),一個(gè)正在運(yùn)行中的線程調(diào)用GetQueuedCompletionStatus時(shí),它就會(huì)立刻從隊(duì)列中取走該完成包。這樣就不存在著環(huán)境的切換,因?yàn)樵撎幱谶\(yùn)行中的線程就會(huì)連續(xù)不斷地從隊(duì)列中取走完成包,而其他的線程就不能運(yùn)行了。
     完成端口的線程并發(fā)量的建議值就是你系統(tǒng)CPU的數(shù)目。在這里,要區(qū)分清楚的是,完成端口的線程并發(fā)量與你為完成端口創(chuàng)建的工作者線程數(shù)是沒有任何關(guān)系的,工作者線程數(shù)的數(shù)目,完全取決于你的整個(gè)應(yīng)用的設(shè)計(jì)(當(dāng)然這個(gè)不宜過大,否則失去了IOCP的本意:))。
第三、IOCP開發(fā)過程中的消息亂序問題。
     使用IOCP開發(fā)的問題在于它的復(fù)雜。我們都知道,在使用TCP時(shí),TCP協(xié)議本身保證了消息傳遞的次序性,這大大降低了上層應(yīng)用的復(fù)雜性。但是當(dāng)使用IOCP時(shí),問題就不再那么簡單。如下例:
  
       三個(gè)線程同時(shí)從IOCP中讀取Msg1, Msg2,與Msg3。由于TCP本身消息傳遞的有序性,所以,在IOCP隊(duì)列內(nèi),Msg1-Msg2-Msg3保證了有序性。三個(gè)線程分別從IOCP中取出Msg1,Msg2與Msg3,然后三個(gè)線程都會(huì)將各自取到的消息投遞到邏輯層處理。在邏輯處理層的實(shí)現(xiàn),我們不應(yīng)該假定Msg1-Msg2-Msg3順序,原因其實(shí)很簡單,在Time 1~Time 2的時(shí)間段內(nèi),三個(gè)線程被操作系統(tǒng)調(diào)度的先后次序是不確定的,所以在到達(dá)邏輯處理層,
Msg1,Msg2與Msg3的次序也就是不確定的。所以,邏輯處理層的實(shí)現(xiàn),必須考慮消息亂序的情況,必須考慮多線程環(huán)境下的程序?qū)崿F(xiàn)。
        在這里,我把消息亂序的問題單列了出來。其實(shí)在IOCP的開發(fā)過程中,相比于同步的方式,應(yīng)該還有其它更多的難題需要解決,這也是與Select方式相比,IOCP的缺點(diǎn),實(shí)現(xiàn)復(fù)雜度高。
結(jié)束語:
    ACE的Proactor Framework, 對(duì)windows平臺(tái)的IOCP做了基于Proactor設(shè)計(jì)模式的,面向?qū)ο蟮姆庋b,這在一定程度上簡化了應(yīng)用開發(fā)的難度,是一個(gè)很好的異步IO的開發(fā)框架,推薦學(xué)習(xí)使用。 
Reference:
    Microsoft Technet,Inside I/O Completion Ports

posted @ 2007-08-26 16:06 愛上龍卷風(fēng) 閱讀(16544) | 評(píng)論 (23)編輯 收藏

僅列出標(biāo)題  下一頁
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            亚洲免费成人av电影| 在线视频你懂得一区| 午夜日韩激情| 欧美一区二区在线看| 国内精品嫩模av私拍在线观看| 久久九九国产| 狼人天天伊人久久| 在线视频日韩精品| 校园春色综合网| 亚洲风情亚aⅴ在线发布| 亚洲高清一二三区| 欧美精品在线免费播放| 亚洲欧美日韩在线综合| 久久黄色影院| 艳女tv在线观看国产一区| 亚洲一区二区欧美| 永久免费精品影视网站| 亚洲精品永久免费| 激情小说亚洲一区| 亚洲精品一品区二品区三品区| 国产精品福利在线| 亚洲大胆女人| 国产精品视频网站| 亚洲国产精品久久久久婷婷884| 欧美日韩成人一区| 久久久久久久久岛国免费| 欧美丰满高潮xxxx喷水动漫| 午夜久久一区| 欧美激情精品久久久久久免费印度| 亚洲一区三区电影在线观看| 久久久久国产精品人| 亚洲专区一区| 欧美国产成人精品| 久久综合影音| 国产精品免费一区二区三区观看| 欧美大片免费看| 国产日韩亚洲欧美| 日韩亚洲精品在线| 亚洲理论在线观看| 久久综合网hezyo| 欧美一级淫片aaaaaaa视频| 欧美大片国产精品| 麻豆精品视频在线观看| 国产女优一区| 亚洲夜间福利| 亚洲亚洲精品在线观看| 在线精品视频一区二区| 中文av一区二区| a91a精品视频在线观看| 久久久久久国产精品mv| 久久激情综合| 国产日韩av一区二区| 亚洲另类在线一区| 99亚洲视频| 欧美激情一二区| 亚洲一区二区三| 久久精品亚洲精品| 国产精品一香蕉国产线看观看| 日韩午夜精品| 亚洲午夜在线| 欧美日韩一区二区三区在线观看免| 亚洲第一精品福利| 亚洲日韩第九十九页| 欧美黄色aaaa| 亚洲伦理在线免费看| 日韩视频免费观看高清完整版| 美女视频黄 久久| 亚洲国产天堂久久综合网| 亚洲第一区在线| 免费人成精品欧美精品| 亚洲激情第一页| 夜久久久久久| 国产精品看片资源| 欧美在线视频导航| 欧美1区2区3区| 亚洲人久久久| 欧美午夜宅男影院在线观看| 亚洲午夜视频在线| 欧美专区亚洲专区| 亚洲国产成人av| 欧美日韩免费视频| 亚洲欧美日韩国产中文| 久久综合九色综合网站| 亚洲激情一区二区| 欧美视频在线视频| 久久精品一区二区三区不卡| 欧美激情二区三区| 亚洲网站视频福利| 国产性色一区二区| 欧美成人xxx| 在线综合亚洲欧美在线视频| 久久精品国产在热久久| 亚洲国产天堂久久综合| 国产精品久久久久久久久| 欧美一区二区在线看| 最新高清无码专区| 亚久久调教视频| 91久久精品国产91久久| 国产精品乱码一区二三区小蝌蚪 | 久久久久久久97| 在线观看一区二区视频| 欧美色中文字幕| 性色一区二区三区| 亚洲精品国产视频| 久久综合伊人77777麻豆| 一区二区三区欧美成人| 国产无遮挡一区二区三区毛片日本| 久久国产精品电影| 日韩视频在线观看国产| 久久综合一区二区| 欧美亚洲网站| 最新日韩中文字幕| 国产亚洲一区二区三区| 欧美三级在线视频| 蜜臀久久99精品久久久久久9 | 亚洲一区二区三区免费在线观看| 国产综合色精品一区二区三区| 欧美成ee人免费视频| 欧美中文字幕在线视频| 亚洲天堂免费在线观看视频| 亚洲激情视频网站| 乱人伦精品视频在线观看| 亚洲欧美乱综合| 亚洲精品国产精品乱码不99| 欧美国产视频在线观看| 一区二区激情| 国内精品久久久久影院 日本资源| 欧美三区在线视频| 久久九九99| 亚洲无人区一区| 亚洲日本激情| 亚洲激情网站免费观看| 久久天天躁狠狠躁夜夜av| 欧美在线啊v| 宅男在线国产精品| 亚洲毛片av| 亚洲精品久久久久中文字幕欢迎你 | 欧美激情aⅴ一区二区三区| 久久久久国产一区二区| 欧美一级淫片播放口| 午夜伦理片一区| 欧美专区亚洲专区| 久久久噜噜噜久久久| 久久久噜噜噜久久中文字免| 久久精视频免费在线久久完整在线看| 欧美一区二区三区精品| 久久国产日韩| 久久影视精品| 欧美国产精品v| 欧美日韩国产123| 国产精品人人做人人爽| 国产区在线观看成人精品| 国产一区导航| 亚洲第一福利视频| 亚洲黄色小视频| 日韩一区二区福利| 亚洲综合丁香| 久久国产66| 久久久久国产精品一区| 蜜臀久久久99精品久久久久久| 欧美国产精品专区| 国产精品热久久久久夜色精品三区 | 中国成人黄色视屏| 欧美一级欧美一级在线播放| 久久久久国产一区二区| 欧美成人中文字幕| 国产精品xxx在线观看www| 国产日韩在线亚洲字幕中文| 在线成人免费视频| 一区二区三区精品国产| 午夜亚洲福利| 欧美大片一区二区| 一区二区高清视频| 久久精品99国产精品酒店日本| 亚洲六月丁香色婷婷综合久久| 日韩午夜激情| 久久gogo国模裸体人体| 欧美激情亚洲另类| 亚洲女性裸体视频| 欧美国产一区二区在线观看| 国产精品亚洲精品| 亚洲麻豆视频| 久久婷婷丁香| 亚洲小视频在线观看| 欧美xxx成人| 国语自产精品视频在线看一大j8 | 国产精品一二一区| 亚洲精选国产| 久久伊人精品天天| 亚洲午夜精品久久| 欧美激情一区在线| 激情另类综合| 欧美一级精品大片| 日韩视频在线一区| 免费观看成人www动漫视频| 国产区二精品视| 亚洲欧美另类在线观看| 亚洲精品美女| 欧美二区不卡| 亚洲国产高清在线观看视频|