• <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>

            sherrylso

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

            #

            摘要:
                    在對多線程并發(fā)的編程環(huán)境下,死鎖是我們經(jīng)常碰到的和經(jīng)常需要解決的問題。所謂死鎖,即:由于資源占用是互斥的,當某個線(進)程提出申請資源后,使得有關進程在無外力協(xié)助下,永遠分配不到必需的資源而無法繼續(xù)運行,這就產(chǎn)生了一種特殊現(xiàn)象死鎖,如下圖:

                    線程#1在獲得Lock A后,需要獲得Lock B,而同時,線程#2在Lock B后,需要獲得Lock A。對于線程#1和#2,由于都不能獲得滿足的條件,而無法繼續(xù)執(zhí)行,死鎖就形成了。
                    死鎖是多線程并發(fā)編程的大難題,我們可以通過Log Trace、多線程編程輔助工具、IDE調(diào)試環(huán)境等手段進行調(diào)試、跟蹤。然而,另一個更難對付的問題是“假死鎖”(我在這里暫且稱為“假死鎖”,實在找不到什么更好的稱呼)。所謂的假死鎖,我給出的定義是:在有限的時間內(nèi)的死鎖。與死鎖不同的是,其持續(xù)的時間是有限的,而大家都知道,死鎖持續(xù)的時間是無限的,如果碰到死鎖,程序接下來是什么都干不了了。而正是由于假死鎖的相對的持續(xù)時間,給我們編程人員會帶來更大的麻煩。可以想象得到,我們想通過某些工具來Trace這樣一個特定的時間段是非常困難的,更多的情況下,我們需要結(jié)合LOG進行合理的分析,使得問題得以解決。本文就假死鎖產(chǎn)生的條件,環(huán)境,以及解決的辦法做一個討論。
            一、假死鎖的產(chǎn)生條件。

                考慮下面的例子(我只是給給出了偽代碼),假設我們系統(tǒng)中的線程個數(shù)是確定的,有限的。在本例中,系統(tǒng)總的線程數(shù)目是3。如下圖:

            線程#1,#2,#3都可能被調(diào)度進入臨界區(qū)A,我們假設線程#1執(zhí)行臨界區(qū)A時花費了10s的時間,而在這10s的時間里,線程#2與線程#3都處于等待的狀態(tài)。也就是說:在這個10s的時間里,系統(tǒng)是沒法響應任何的其他請求。我們稱之為10s的假死鎖。如果在這段時間里,系統(tǒng)需要一些關鍵的請求被執(zhí)行,這些關鍵請求是需要real time地被處理,比如說是Timer事件,則后果是不堪設想的。(注意:我們的假定是系統(tǒng)中的線程只有#1,#2,#3)。
                   以此,總結(jié)一下發(fā)生假死鎖的條件,如下:
            --〉臨界區(qū)的代碼在集中的時間段內(nèi),可能被系統(tǒng)中的任意線程執(zhí)行,完全由操作系統(tǒng)決定。
            --〉臨界區(qū)的代碼在某些情況下,可能是很耗時的。(比如:其執(zhí)行時間大于100ms,或者,甚至是秒級別的)
            二、在Proactor(IOCP)中的假死鎖。
                    在前面的文章中,我提到過在windows平臺上,Proactor設計模式是基于IOCP的。在這里,本文不會用過多的語言來闡述Proactor是怎樣的設計,重點放在Proactor的假死鎖及其一些解決的辦法。另外需要說明的是,我這里所說的Proactor,在技術層面上,等同于IOCP,我們也可以按照IOCP來理解我所闡釋的概念。
                    我們都知道,IOCP是靠工作者線程來驅(qū)動的。工作者線程與一個完成端口對象相關聯(lián),當IO 請求被投遞到完成端口對象時,這些線程為完成端口服務。需要說明的是,應該創(chuàng)建多少個線程來為完成端口服務,是你的應用設計來決定的(很重要的的一點是:在調(diào)用CreateIoCompletionPort時指定的并發(fā)線程的個數(shù),和創(chuàng)建的工作者線程的個數(shù)是有區(qū)別的,詳細的技術細節(jié),請參考其他資料)。但是總的來說,在你的系統(tǒng)交付運行后,工作者線程的線程數(shù)目是一個確定的值。其結(jié)構(gòu)圖,大致如下:

                     我們假定使用了線程數(shù)目為4的工作者線程來為完成端口服務,它們通過調(diào)用來GetQueuedCompletionStatus方法來從完成端口中獲取IO相關的packet,一旦獲得,它們都會回調(diào)業(yè)務邏輯層的代碼來進行相關的業(yè)務邏輯處理。到這里我們看到,假設,在業(yè)務邏輯層存在臨界互斥區(qū),并且在某一個集中的時間段內(nèi),工作者線程都可能被調(diào)度執(zhí)行該臨界互斥區(qū),那么,假死鎖的條件基本形成,如果某一個線程在該區(qū)域花費的時間比較長,假死鎖就會發(fā)生。
                    一般來說,解決這樣的問題的關鍵就是打破形成假死鎖的條件:
                   第一、在回調(diào)函數(shù)里,盡量減少鎖的使用。
                   第二、減量減少臨界互斥區(qū)的執(zhí)行時間。對于一些慢速的操作尤其注意。比如:當你在臨界互斥區(qū)訪問慢速的IO操作時(打開文件,讀寫文件等),可能需要考慮Cache機制,通過使用內(nèi)存來代替慢速的disk。
                   第三、將臨界互斥區(qū)代碼委托給另外獨立的線程(或線程組)執(zhí)行,代價是增加這些線程間的通訊。
                   第四、通過使用流控等手段,避免讓所有的線程在集中的時間段內(nèi)訪問該臨界互斥區(qū)。
            三、結(jié)束語:

                     事實上,類似這樣的問題,一旦存在,是很難發(fā)現(xiàn)和調(diào)試的。不過對于多線程的編程,我們都應該遵守以下的基本原則,以最大化的防止死鎖和假死鎖的發(fā)生。

                     --> 盡量減少鎖的使用頻率和保護范圍。
                     --> 當線程在互斥鎖的保護范圍內(nèi)執(zhí)行代碼時,應該:盡量減少對慢速IO設備的訪問(如:disk),盡量避免獲得其它互斥資源。
                     --〉正確使用各種鎖,包括:原子操作原語,Read Lock, Write Lock, 和Recursive Lock等。這些鎖在不同的場景下有著不同的作用。
            posted @ 2007-08-12 15:41 愛上龍卷風 閱讀(4097) | 評論 (9)編輯 收藏

                 一般來說,基于CS(client-server)軟件架構(gòu)的開發(fā)技術有很多種。比較常用的有:基于socket的網(wǎng)絡編程、RPC、基于Java技術的RMI(當然C#也有類似技術)、CORBA等。在這里我們只是對基于socket的網(wǎng)絡編程與RMI作個對比,有助于我們了解它們各自的應用領域,幫助我們在面對一個具體問題的時候選用適合的技術。另外,本文所做的討論可以認為是脫離了語言層面的東西,只是對技術的本身做一個討論,無關乎你是用C++、C#或Java 在開發(fā)。
            一、RMI技術簡介
                    本文就以Java為例,簡單介紹一下RMI技術。
                    從Java1.1開始,遠程方法調(diào)用作為Java分布式對象技術成為Java核心的API之一(在java.rmi.* 包)。RMI的引入,使得Java程序之間能夠?qū)崿F(xiàn)靈活的,可擴展的分布式通信。RMI允許Java對象存在于多個不同的地址空間,分布在不同的Java虛擬機上。每一個地址空間可以在同一臺主機上或者網(wǎng)絡上不同的計算機上。由于遠程方法調(diào)用跨越不同的虛擬機邊界到不同的指定的地址空間,所以沒有對象共享的全局變量,這就需要對象序列化(Object Serialization)API,它使得Java對象能夠在不同的JVM之間傳遞。對象序列化是特別為Java的對象設計的,這就意味著Java程序中的對象可以作為對象參數(shù)存取(可序列化的對象必須實現(xiàn)Serializable接口)。結(jié)合RMI和對象序列化機制,就可以訪問越過本地Java虛擬機邊界的對象以及數(shù)據(jù)。通過RMI,可以調(diào)用遠程對象的遠程方法,而通過Java對象序列化機制可以將對象傳遞給這些方法。
                    最基本的Java模型并沒有提供將遠程主機上的Java對象看作本地Java程序地址空間一部分的能力,而RMI禰補了這一不足。另外,由于Java與硬件平臺無關的特性,無論是同構(gòu)的系統(tǒng)還是異構(gòu)的系統(tǒng),RMI不需移植就可以順利運行。
                   RMI為Java平臺的分布式計算提供了一個簡單而直接的模型。因為Java的RMI技術是基于Java平臺的,所以它將Java平臺的安全性和可移植性等優(yōu)點帶到了分布式計算中。RMI大大擴展Java的網(wǎng)絡計算能力,它為編寫基于分布式對象技術的企業(yè)級Internet/Intranet應用提供了強大的系統(tǒng)平臺支持。
                  Java RMI體系結(jié)構(gòu)如下圖:


            二、基于socket的網(wǎng)絡編程
                    當你使用socket進行網(wǎng)絡應用開發(fā)的時候,一般的思路是“消息驅(qū)動邏輯”,即這樣的軟件系統(tǒng)一般具有以下特點:
                   (1) 客戶端與服務器端依靠消息進行通訊。
                   (2) 客戶端或者服務器端都需要一個消息派遣器,將消息投遞給具體的massage handler
                   (3) 客戶端或者服務器端利用massage handler進行邏輯事務處理
             見下圖:

                    使用socket開發(fā)的軟件系統(tǒng),從技術的本質(zhì)上來講,有以下幾個特點:
                    (1) 基于TCP協(xié)議的通訊
                    (2) 應用程序本身需要提供對消息的序列化處理(所謂的序列化指的是將消息輸出到網(wǎng)絡流中)
                    (3) 客戶端與服務器端需要事先商議好它們之間的通訊協(xié)議即它們交互的消息格式
                    (4) 由于是消息驅(qū)動邏輯,從本質(zhì)上決定了這樣的編程模式很難面向?qū)ο蠡?br>三、RMI Vs Sochet
                    RMI技術比較socket的網(wǎng)絡編程主要有以下幾個方面:
                    第一、.RMI是面向?qū)ο蟮模笳卟皇恰?br>        第二、.RMI是與語言相綁定的。比如當你使用Java RMI技術的時候,客戶端與服務器端都必須使用Java開發(fā)。而socket的網(wǎng)絡編程是使用獨立于開發(fā)語言的,甚至獨立于平臺。基于socket的網(wǎng)絡編程,客戶端與服務器端可以使用不同開發(fā)語言和不同的平臺。
                   第三、從網(wǎng)絡協(xié)議棧的觀點來看,RMI與socket的網(wǎng)絡編程處于不同層次上。基于socket的網(wǎng)絡編程位于TCP協(xié)議之上,而RMI在TCP協(xié)議之上,又定義了自己的應用協(xié)議,其傳輸層采用的是Java遠程方法協(xié)議(JRMP)。可見,在網(wǎng)絡協(xié)議棧上,基于RMI的應用位置更高一些,這也決定了,與socket的網(wǎng)絡編程相比,RMI會喪失一些靈活性和可控性,但是好處是它帶給了應用開發(fā)者更多的簡潔,方便和易用。比如:如果你用的是RMI,你不需要關心消息是怎么序列化的,你只需要像本地方法調(diào)用一樣,使用RMI。代價是:應用開發(fā)者無法很好地控制消息的序列化機制。
                  第四、這是最后一點不同,我認為也是比較重要的一點,就是兩種方法的性能比較,其往往決定著你將使用那種技術來開發(fā)你的應用。以下引用Adrian Reber在Network-programming with RMI文中對TCP和RMI所做的一個比較,其做的實驗主要是對兩者在網(wǎng)絡傳輸?shù)膸捝献鞯膶Ρ龋?在網(wǎng)絡上傳輸2 byte的有效數(shù)據(jù),對于TCP而言,總共有478 byte被額外傳輸,而對于RMI, 1645byte被額外傳輸。
            以下是兩者的trace結(jié)果:
            TCP:
            46037 > 12345 [SYN] Seq=801611567 Ack=0 Win=5840 Len=0
            12345 > 46037 [SYN, ACK] Seq=266515894 Ack=801611568 Win=10136 Len=0
            46037 > 12345 [ACK] Seq=801611568 Ack=266515895 Win=5840 Len=0
            12345 > 46037 [PSH, ACK] Seq=266515895 Ack=801611568 Win=10136 Len=1
            46037 > 12345 [ACK] Seq=801611568 Ack=266515896 Win=5840 Len=0
            12345 > 46037 [FIN, PSH, ACK] Seq=266515896 Ack=801611568 Win=10136 Len=1
            46037 > 12345 [RST, ACK] Seq=801611568 Ack=266515898 Win=5840 Len=0
            RMI:
            42749 > rmiregistry [SYN, ECN, CWR]
            Seq=3740552479 Ack=0 Win=32767 Len=0
            rmiregistry > 42749 [SYN, ACK, ECN]
            Seq=3749262223 Ack=3740552480 Win=32767 Len=0
            42749 > rmiregistry [ACK] Seq=3740552480 Ack=3749262224 Win=32767 Len=0
            JRMI, Version: 2, StreamProtocol
            rmiregistry > 42749 [ACK] Seq=3749262224 Ack=3740552487 Win=32767 Len=0
            JRMI, ProtocolAck
            42749 > rmiregistry [ACK] Seq=3740552487 Ack=3749262240 Win=32767 Len=0
            Continuation
            rmiregistry > 42749 [ACK] Seq=3749262240 Ack=3740552506 Win=32767 Len=0
            JRMI, Call
            rmiregistry > 42749 [ACK] Seq=3749262240 Ack=3740552556 Win=32767 Len=0
            JRMI, ReturnData
            42749 > rmiregistry [ACK] Seq=3740552556 Ack=3749262442 Win=32767 Len=0
            JRMI, Ping
            JRMI, PingAck
            42749 > rmiregistry [ACK] Seq=3740552557 Ack=3749262443 Win=32767 Len=0
            JRMI, DgcAck
            42749 > rmiregistry [FIN, ACK]
            Seq=3740552572 Ack=3749262443 Win=32767 Len=0
            rmiregistry > 42749 [FIN, ACK]
            Seq=3749262443 Ack=3740552573 Win=32767 Len=0
            42749 > rmiregistry [ACK] Seq=3740552573 Ack=3749262444 Win=32767 Len=0
                    實驗的結(jié)果是:RMI與TCP based socket相比,傳輸相同的有效數(shù)據(jù),RMI需要占用更多的網(wǎng)絡帶寬(protocol overhead)。從這里,我們可以得出一個一般性的結(jié)論:RMI主要是用于遠程方法的”調(diào)用“(RMI是多么的名符其實:)),其技術內(nèi)涵強調(diào)的是“調(diào)用”,基于此,我能想到的是:移動計算,和遠程控制,當你的應用不需要在client與server之間傳輸大量的數(shù)據(jù)時,RMI是較好的選擇,它簡潔、易于開發(fā)。但是,一旦你的應用需要在client與server之間傳輸大量的數(shù)據(jù),極端的,比如FTP應用,則RMI是不適合的,我們應該使用socket。

            四、參考資料:
            Network-programming with RMI, by Adrian Reber, URL:
            http://42.fht-esslingen.de/~adrian/master/rmi.pdf
            posted @ 2007-07-28 19:06 愛上龍卷風 閱讀(5638) | 評論 (2)編輯 收藏

                 摘要: 在windows平臺下,用于對多線程(包括進程)之間的同步保護機制,基本上有這么幾種:
            1)Critical Section對象 2)Event對象 3)Mutext 對象 4) Semaphore對象。網(wǎng)上已經(jīng)有很多的文章在介紹這些對象是怎么使用的。本文的著眼點在于:總結(jié)出這些同步保護機制的一些明顯的行為特征,而這些行為特征,也是我們再寫程序時經(jīng)常會碰到的。  閱讀全文
            posted @ 2007-07-22 21:10 愛上龍卷風 閱讀(4905) | 評論 (6)編輯 收藏

            不知道大家是否有同感,在開發(fā)軟件應用系統(tǒng)的過程中我們經(jīng)常面臨一個非常tedious的問題,即如何分類和處理軟件行為產(chǎn)生的錯誤值,大致有這樣幾個方面的問題:
                    第一.錯誤值如何分類。參考microsoft的開放文檔,按照錯誤值的嚴重程度分為critical、error、warning、information等四個級別。
                    第二.錯誤值如何在各個軟件子模塊中有效的表示和溝通。
                    第三.如何劃分出用戶所關心的錯誤值集合。
            這篇文章對錯誤類型定義與處理作了很好的闡釋。推薦給大家,大家如果有什么好的開發(fā)經(jīng)驗,歡迎探討。
            FYI:
            Taxonomy of Error Types and Error Handling
            Posted: Jun 21, 2007 8:13 PM

            Most of what is written about error and exception handling is fairly abstract and vague. When specific recommendations are made, those usually consist of examples given for specific circumstances. Yet, error handling is a fundamental task for developers. This brief review attempts to categorize the different types of error conditions a program can encounter. By describing these categories, I also provide suggestions on how to handle each type of error condition.

            In general, errors a program can encounter tend to be the result of one of three things:

            • Restrictions: Arguments to a routine that can never work, and always result in an error, define a restriction on the use of the routine. Ensuring that only correct arguments are passed to a routine is the type of thing that programming by contract is meant to address.
            • Inconsistencies: When values or resources are not what they are expected to be, or are missing, that creates an inconsistency between the expected state of the environment and the actual state. This may be the internal environment, such as a null pointer, or the external environment, such as a corrupt file. It doesn't encompass inconsistencies in the data model, which often needs to be temporarily inconsistent during an operation (e.g. adding a node to a linked list).
            • Failures When an operation simply does not work, and it's out of the program's control, this is a failure. For example, a pulled network cable.

            These types of errors overlap to an extent (say, a working network could be considered part of the expected state, making it an inconsistency error). In general, though, most errors can fall into one of these categories.

            Sometimes failures are not errors, and are just a way of detecting the current external state. For example, opening a file that doesn't exist may fail, but results in an error only when that file is actually needed.

            Error Handling Responsibilities

            Program code is responsible for the consistency of the internal program state. Generally certain code has primary (ideally, exclusive) responsibility for parts of the internal state. Inconsistency errors that occur within the code responsible for that state are bugs.

            Sometimes the state management responsibility is shared between different sections of code. This is a bad idea, because it makes assigning responsibility for an inconsistency error harder, but it does happen in practice.

            It's important to make a distinction between error detection and debugging. Often, data generated in the process of error handling is mixed together with diagnostic information. If possible, these types of information should be kept completely separate—at least conceptually, even if combined in a single data structure.

            Safe Zones

            Restrictions can be checked before calling a routine, or within a routine. It seems a waste of time to check arguments every time a routine is called when you already know those arguments are correct. One strategy is to separate parameter checking from parameter usage. This doesn't work reliably for library code, where anything can happen between the check and the use of the parameters, but within a base of code for a particular application or within a library, you can restrict the code to not change a value known to be safe.

            The code between a parameter check and the next change to a parameter variable is a safe zone, where parameters don't have to be re-checked. This is only valid for restriction errors, because inconsistency and failure errors can be caused by things outside the code's safe zone. Things like critical sections (in multithreaded environments), semaphores and file locks are meant to create a very limited kind of safe zone for inconsistency and failure errors.

            The code safe zones for parameters can overlap with others, and may not be well defined. One way to deal with this is to assign known safe values to variables which indicate this safety. Joel Spolsky wrote about one way to do this using variable naming conventions in Making Wrong Code Look Wrong. Safe values should be assigned to variables declared constant.

            Reporting Errors

            Code calling a routine needs to know three things to decide how to proceed: First, whether the data is returned, if any, or if the method invocation succeeded; second, whether an error occurred; and, third, whether the error is permanent or transitory. This defines the following possible error states returned from a routine:

            1. Successful
            2. Restriction error (always permanent)
            3. Permanent (bug) inconsistency
            4. Transitory (detected) inconsistency
            5. Failure (transitory for all we know)

            It's often a bad idea to mix an error code with a return value, such as designating a specific values—say, 0 or -1—to be invalid. Some languages, like Python, allow multiple values to be returned from a method as tuples. A tuple is basically an anonymous class, and can be implemented in a language like Java by defining a class for objects returned by a method, or in C by defining a struct which is passed as a parameter and is updated by the function. But in many cases, exceptions are a much better way to separate error information from return values.

            Exceptions transmit an object from the exception location to a handler in a scope surrounding it, or surrounding the point where the routine was called. The exception objects include information by the object type and class, and debugging information the data contained within that type. Exceptions by themselves don't indicate the error state, so that must be included as an attribute of the exception object, or the error state must be deduced from the debugging information (object type and data).

            Java introduced the controversial notion of checked exceptions, which must either be caught or declared to be thrown by a method in order to compile, while unchecked (or runtime) exceptions behave like exceptions in other languages. The main cause of the controversy is that there has been no good definition of why there should be a difference and, as a result, no consistent strategy in the implementation in various libraries, including standard parts of the different Java runtime libraries.

            In general, unchecked exceptions are meant for bugs, where an error indicates that the code is simply wrong and must be fixed (restriction and bug inconsistency errors). An example is a NullPointerException. Checked exceptions are for detected inconsistency and failure errors, where the program may have a strategy of handling the error. An example is an I/O error.

            Transactional Operations

            One strategy to handle errors is to make all operations transactional, so that if they fail, it's as if the operation was never tried. One way implement this is to define an "undo" operation for every change:

            Ideal transactional function

            In this example, the functions are also transactional, and thus don't need to be rolled back if they fail. This can be done with nested if/else blocks, or with nested try/catch blocks. If the "undo" operations themselves have errors, the result looks more like this:

            Realistic transactional function

            One way of dealing with this is to modify a copy of the program state, and if all operations succeed, only then commit the changes. The commit may fail, but this isolates the possible state changing errors to one point, and is similar how databases implement transactions. Another way to implement transactional operations is to make a copy of before any state is changed, and use that copy to restore the expected state, in case of an error.

            In summary, having a clear taxonomy of error conditions that code may encounter helps develop better strategies for dealing with, and possibly recovering from, those errors.

            posted @ 2007-07-15 18:31 愛上龍卷風 閱讀(1133) | 評論 (0)編輯 收藏

            一、異步IO
                    對于應用程序而言,有兩種類型的IO調(diào)用:即同步IO與異步IO。其本質(zhì)的區(qū)別是:同步IO會block當前的調(diào)用線程,而異步IO則允許發(fā)起IO請求的調(diào)用線程繼續(xù)執(zhí)行,等到IO請求被處理后,會通知調(diào)用線程。在windows平臺上,應用程序可以調(diào)用CreateFile API, 并通過設置FILE_FLAG_OVERLAPPED標志來決定是否發(fā)起異步IO請求。
                    對于異步的IO請求,其最大的好處是:慢速的IO請求相對于應用程序而言是異步執(zhí)行,這樣可以極大提高應用程序的處理吞吐量。發(fā)起IO請求的應用程序需要關心的是IO執(zhí)行完成的結(jié)果,而不必忙等IO請求執(zhí)行的過程。
                   事實上,無論對于同步IO,還是異步IO,當IO請求發(fā)送到device driver后,device driver的執(zhí)行總是異步的,當它接到IO請求之后,總會馬上返回給IO System。而IO System是否立即返回給調(diào)用線程,則取決于FILE_FLAG_OVERLAPPED標志的設置,如下圖:


            二、異步IO的同步問題。
                    我們使用異步IO,是為了提高應用程序的處理吞吐量。但是,當異步IO不再異步時(無論你是否設置FILE_FLAG_OVERLAPPED標志),應用程序的性能會受到極大的影響。根據(jù)Microsoft Knowledge Base 156932, 在下列幾種情況下,異步IO會失去它的異步性,而表現(xiàn)出同步的性質(zhì):
            1)如果文件使用了NTFS compression壓縮,則system driver不會異步地存取這樣的文件。
            2)擴展文件長度的IO操作不會是異步操作。
            3)Cache機制。如果IO操作使用了file system cache,則這樣的IO操作會被當成同步IO,而非異步IO。
            即使你使用了FILE_FLAG_OVERLAPPED標志。在這種情況下,
            a.如果需要讀取的數(shù)據(jù)已經(jīng)在Cache里,那么I/O drivers會認為這樣的IO請求可以被立即處理,其結(jié)果是ReadFile 或者WriteFile調(diào)用返回TRUE,表示是:同步處理完成。
            b.如果需要讀取的數(shù)據(jù)不在Cache里,windows NT的file system是使用page-faulting機制來實現(xiàn)cache管理,而page-faulting總是被同步處理, Windows NT沒有提供異步的page-faulting機制。的確, file system driver使用了線程池來緩解這一問題,但是,當應用程序發(fā)起的IO請求足夠多時,線程池還是不能應付的。
                    在我們開發(fā)基于異步IO應用程序時,應該避免上述問題的出現(xiàn),因為它們會使程序的性能大打折扣。
            那么,對于Cache,我們?nèi)绾伪苊饽兀看鸢甘牵赫埵褂肍ILE_FLAG_NO_BUFFERING標志。這個標志會使異步IO真實地異步執(zhí)行。
            三、性能的測試數(shù)據(jù)(僅供參考)。
                  我在我的機器上,簡單地對使用FILE_FLAG_NO_BUFFERING標志的異步IO,與不使用FILE_FLAG_NO_BUFFERING標志的異步IO進行了對比。
            操作:順序讀取1G的文件。
            x軸表示:每次讀取的字節(jié)數(shù)(單位:K/每次)
            Y軸表示:讀取完成所需要的時間。(單位:millisecond)
            注意:每次測試讀取的內(nèi)容總數(shù)是相等的(1000M)。
            例如:如果每次讀取128k,則需要讀取8000次(128k*8000 = 1000M)。
            如果每次讀取256k,則需要讀取4000次(256k*4000 = 1000M)。
            粉紅色的線沒有使用FILE_FLAG_NO_BUFFERING標志,而黃色的線使用了FILE_FLAG_NO_BUFFERING標志。

            從以上的數(shù)據(jù),我們可以得出以下結(jié)論:
            1) 當使用FILE_FLAG_NO_BUFFERING標志,應用程序的性能會極大提高,大概有50%的提高。
            2)在使用異步IO的時候,還有一個注意的問題是:當你每次讀取的字節(jié)數(shù)增大的時候,性能也會提高。尤其在小于1024k時,當增大次讀取的字節(jié)數(shù),性能都有明顯的提高。在混合了網(wǎng)絡傳輸?shù)葟碗s因素的應用程序開發(fā)過程中,建議將該值設置為可配置的參數(shù),通過調(diào)整該參數(shù),使你的應用達到最好的性能。

            參考資料:

            1) Microsoft Knowledge Base 156932

            2)  Microsoft Windows Internals, Fourth Edition. 

            posted @ 2007-07-01 18:45 愛上龍卷風 閱讀(9032) | 評論 (6)編輯 收藏

                    作為程序員,一直困擾我的一個問題是:一名優(yōu)秀的程序員,應該是注重面向?qū)ο蠓治瞿芰Φ呐囵B(yǎng),還是注重算法分析能力的培養(yǎng)。我相信,這也是一個很多人面臨的問題。我的感覺是:很多system level的程序員更加側(cè)重于算法,而application level的程序員,更多的傾向于討論面向?qū)ο蟆4蠹乙部梢钥吹剑芏嘀鸌T公司的面試,比如google,比如微軟,很喜歡考察程序員的算法方面的能力。而自從設計模式理論風靡IT界以來,好像這些狀況有些改變,他們開始考察設計模式相關的問題,考察程序員面向?qū)ο蟮姆治瞿芰Α2豢煞裾J的是,設計模式理論,其基于面向?qū)ο蟮睦碚摷夹g,提供了開發(fā)者非常實效,有用的解決問題的模式。依賴于問題的上下文,應用設計模式,開發(fā)者可以開發(fā)出更加"面向?qū)ο?的系統(tǒng)。
               設計一個復雜的系統(tǒng)的本質(zhì),就是:將復雜的問題分解成小的,為我們所理解的問題,然后分而治之。人類的智力是有限的,當我們在面對一個復雜問題的時候,總會習慣于首先將他分解,分解到問題足夠的簡單,足夠為我們所理解,解決。事實上,無論是面向?qū)ο螅€是算法,它們都是分解復雜問題的方法與手段。是采用面向?qū)ο蟮姆椒ㄈシ治觯€是使用算法的分析方法,完全是由客觀的主體決定的。非常遺憾的是,這兩類分析方法是互斥的,排他的,你是不可能同時使用這兩種方法的分析解決問題。我們先看一個簡單的例子:
            問題的定義:client和server使用TCP/IP進行一個簡單的交互。
            算法的分解方法如下:


            問題空間被分解成為幾個執(zhí)行步驟,accept,connet,send,recieve。
            面向?qū)ο蟮姆纸夥椒?/u>如下:


            問題空間被分解成為幾個對象:c_connector, 主要負責建立TCP連接,在連接成功后,會得到一個c_socket_stream對象,該對象負責主要負責發(fā)送和接收網(wǎng)路數(shù)據(jù)。c_acceptor,負責監(jiān)聽網(wǎng)絡連接請求,在一個TCP連接成功建立后,返回給調(diào)用者一個c_socket_stream。
            兩者的區(qū)別在于:兩者分解方法的著重點是不同的,算法的分析方法強調(diào)的是事物內(nèi)部各類事件之間的順序,依賴,耦合關系。算法所關心的是事件本身,例如上例中:它關心的是send,recv這樣發(fā)生在事物內(nèi)部的事件,以及它們之間的調(diào)度關系。面向?qū)ο蟮姆治龇椒ㄔ谟趶娬{(diào)的是事物內(nèi)部各類客觀的主體,以及它們之間的相互協(xié)助。
            從這點上可以看到:在分解一個問題的時候,算法偏重于微觀,面向?qū)ο髠?cè)重于宏觀;算法偏重于細節(jié),面向?qū)ο髠?cè)重于整體。可以看到,我們很容易得出這樣的結(jié)論:當面對一個復雜的問題的時候,我們的直覺會告訴我們,我們會更加傾向于使用面向?qū)ο蠓椒ɡ碚搧矸治鰡栴}。這也是幾十年來面向?qū)ο蟮能浖嵺`經(jīng)驗告訴我們的真理。在計算機應用開發(fā)領域,面向領域問題本身的復雜性(這包括許多方面:比如你的需求在不斷變化,你的應用方式在不斷變化等等),決定了其更適合使用面向?qū)ο蟮姆椒▉矸治鰡栴}。面向?qū)ο蟮能浖到y(tǒng)會更加的富有彈性,更加的能適應這種快速的變化。
                 如何做面向?qū)ο蟮脑O計分析?關鍵在于:
                 1) 對復雜問題的抽象,將復雜的問題抽象成為一組對象,就是我們熟知的objects。object是面向?qū)ο筌浖到y(tǒng)的行為主體。抽象也意味著我們應該忽略細節(jié)的東西,注重整體的東西。
                 2)組織這些objects,使他們形成具有一定結(jié)構(gòu)的整體。比如:通過繼承,使它們成為父子關系,通過組合,使它們具有合作依賴關系。通過組織這些objects,我們更加能清楚地看到這些這些objects公共的行為和屬性。這就形成了面向?qū)ο筌浖玫幕A。
                很多人說:算法是程序設計的靈魂,但是我們也不能忘記;面向?qū)ο螅瑤椭覀兡軌蚋尤菀桌斫鈫栴}復雜性的本質(zhì)。或許算法與面向?qū)ο蟮淖罴训慕Y(jié)合點在于: 使用面向?qū)ο蟮姆椒ǚ纸鈫栴},而使用精良的算法解決問題。

            posted @ 2007-06-24 22:31 愛上龍卷風 閱讀(1871) | 評論 (7)編輯 收藏

            一般來說,網(wǎng)絡應用系統(tǒng)服務器的實現(xiàn),我們從設計模式的角度看,有兩種設計方案可供選擇:
            Reactor服務器,或者Proactor服務器。無論是Reactor,或者Proactor, 都是基于事件驅(qū)動的架構(gòu)設計(Event-driven architecture), 它們的核心是思想是:分離網(wǎng)絡事件的監(jiān)視,驅(qū)動與事物本身的邏輯處理。我們能看到的是:對任何的網(wǎng)絡應用而言,其對網(wǎng)絡事件的監(jiān)視,處理往往是大同小異的,是可以分離出來作為可復用組件而存在的。
            對所有這些網(wǎng)絡應用,所不用的是對網(wǎng)絡請求的邏輯處理。不同于Proactor, 或者Reactor,一般的設計方法是:
             

            而Proactor, 或者Reactor,設計的精髓是:


            別看這一點小小的變化,這是所謂的Hollywood Principle: 'Don't call us, we'll call you'。這是Framework設計者慣用的設計技巧,依賴倒轉(zhuǎn)(The Dependency-Inversion Principle):
            a. 高層模塊不應該依賴于低層模塊,應當依賴于抽象。
            b. 抽象不應該依賴于細節(jié),細節(jié)依賴于抽象。
            言歸正傳,我們來具體談談Reatctor and Proactor.
            一 、Reactor服務器實現(xiàn)。
            在Reactor的實現(xiàn)可分為兩層:
            a.Demultiplexing/Dispatching層:獨立于應用邏輯層,捕獲,分發(fā)網(wǎng)絡請求給具體的網(wǎng)絡請求處理器。
            在這里注意的是,Demultiplexing和Dispatching是兩個不同的概念,Demultiplexing指的是網(wǎng)絡請求的偵聽,及其偵聽以后尋找適合的處理器。Dispatching指的是對處理器的回調(diào)。
            b.應用邏輯層組件:主要處理與應用相關的事物處理。
            其基本的結(jié)構(gòu)圖為


            其各個組成模塊的功能為:

            --〉句柄: 調(diào)用操作系統(tǒng)API獲得,用于identify事件源,如:網(wǎng)絡連接,打開的文件。
            --〉同步事件分離器:操作系統(tǒng)功能,通過API提供,典型的如: select調(diào)用,如windows平臺上的WaitForMultileObj
            --〉事件處理器:這是Demultiplexing/Dispatching層定義的抽象借口,是Demultiplexing/Dispatching層與應用邏輯層之間的協(xié)議約定。應用邏輯層依賴于該抽象借口。
            --〉具體事件處理器:事件處理器的具體實現(xiàn),一般來說,由應用邏輯層組件實現(xiàn)。
            --〉Reactor:提供接口給應用邏輯層,注冊或者反注冊事件處理器,運行應用的事件循環(huán)(反復調(diào)用同步事件分離器),驅(qū)動Demultiplexing/Dispatching層。

            二 、Proactor服務器實現(xiàn)。
            在Proactor的實現(xiàn)也分為兩層:
            a.Demultiplexing/Dispatching層:獨立于應用邏輯層,捕獲,分發(fā)網(wǎng)絡請求給具體的網(wǎng)絡請求處理器,不同于Reactor的是,這一層的策略處理
            基于異步操作,從實現(xiàn)的復雜度上,要復雜于Reactor。
            b.應用邏輯層組件:主要處理與應用相關的事物處理。
            Proactor基本的結(jié)構(gòu)圖為


            --〉句柄: 調(diào)用操作系統(tǒng)API獲得,用于identify事件源,如:網(wǎng)絡連接,打開的文件。與Reactor不同的是,由于Proactor操作是異步的,所以,與每一個異步IO操作項關聯(lián)的,有一個完成事件(Completion Event),當一個異步的IO操作執(zhí)行完成后,一個操作系統(tǒng)的IO子系統(tǒng)會產(chǎn)生一個完成事件。
            --〉異步操作:一個異步操作可以是一個服務請求的具體實現(xiàn),如從連接的socket上讀入數(shù)據(jù),或者寫入數(shù)據(jù)到本地磁盤。異步操作的發(fā)起執(zhí)行,并不會由于慢速的IO而阻塞調(diào)用線程,調(diào)用線程在發(fā)起異步操作之后,可以去做別的事情。這是Proactor能增大服務器處理吞吐量的關鍵所在。我們付出的代價是,開發(fā)的復雜度要比Reactor。
            --〉完成 事件處理器:Demultiplexing/Dispatching層定義的抽象借口。由應用邏輯層組件繼承來實現(xiàn)這些接口
            函數(shù)。
            --〉具體完成事件處理器:事件處理器的具體實現(xiàn)。
            --〉異步操作處理器:由native的操作系統(tǒng)實現(xiàn)。當一個異步操作完成執(zhí)行時,異步操作處理器會產(chǎn)生一個完成事件,然后把這個完成事件插入到完成事件隊列。
            --〉完成事件隊列:用于保存異步操作完成事件的隊列。在windows平臺上,就是我們所說的完成端口。
            --〉異步事件分離器: 操作系統(tǒng)功能,通過API提供。在windows平臺上,就是GetQueuedCompletionStatus()。異步事件分離器等待在完成事件隊列上,當有完成事件被插入到完成事件隊列,異步事件分離器會從完成事件隊列取出該完成事件,返回給調(diào)用者。
            --〉Proactor:運行應用的事件循環(huán)(反復調(diào)用異步操作處理器),驅(qū)動Demultiplexing/Dispatching層。
            --〉發(fā)起者:用于發(fā)起異步IO操作。
            三 、選擇?是Reactor,還是Proactor。
            在我們實現(xiàn)我們的服務器,是選擇Reactor,還是Proactor,個人認為需要考慮的因素是:
            1)對于Reactor而言,好的地方是開發(fā)的復雜度小于Proactor,但缺點同樣明顯:
            第一,基于Reactor實現(xiàn)的服務器可擴展性不如Proactor,這是由其本質(zhì)決定的。在wiondows平臺上,無論是
            Select,或者是WaitForMultiObj,其等待的最大handle數(shù)有一個最大的上限值(默認值是64)。
            第二,基于Reactor實現(xiàn)的服務器服務請求處理的吞吐量不如Proactor好。特別是當對客戶端的請求處理需要更長的時間時,在Reactor的Demultiplexing/Dispatching層,由于Demultiplexing和Dispatching被順序化處理,這樣的話,很容易成為服務器性能的瓶頸。
            2)對于Proactor,如果你需要一個高性能的,高吞吐量的,并發(fā)服務器,Proactor絕對是首選的解決方案,它的問題主要在開發(fā)的難度比較大。
            不過,總的來說,我們可以看到,基于模式設計的Reactor和Proactor,都很好地把Application-Specific的處理邏輯從網(wǎng)絡,IO事件的Demultiplexing/Dispatching中分離出來,對應用程序而言,由于這樣的松耦合關系,我們的應用完全可以在Reactor和Proactor中自由切換,而不需要修改任何的代碼。、
            四 、后記。
            ACE Framework,對Reactor和Proactor設計模式給出了經(jīng)典的實現(xiàn)。請參考:
            http://www.cs.wustl.edu/~schmidt/
            個人覺得是很好的學習材料。

            posted @ 2007-06-10 20:19 愛上龍卷風 閱讀(1046) | 評論 (1)編輯 收藏

                 摘要: 1. 什么是Multi-methods. 在闡述這個概念之前,我們先看一下什么是多態(tài)(Polymorphisn)。多態(tài)是面向?qū)?象程序設計的一個重要的特征, 多態(tài)是允許你將父對象的指針(或者引用)設置成為它的子對象的技術,賦值之后,該父對象指針(或者引用)就可以根據(jù)當前賦 值給它的子對象的特性以不同的方式運作。簡單的說,就是一句話:允許將子類類型的指針賦值給父類類型的指針。多態(tài)性在...  閱讀全文
            posted @ 2007-05-06 22:28 愛上龍卷風 閱讀(2200) | 評論 (4)編輯 收藏

            僅列出標題
            共2頁: 1 2 
            国产精品一区二区久久精品无码 | 中文字幕久久波多野结衣av| 欧美国产成人久久精品| 久久WWW免费人成一看片| 精品久久久无码人妻中文字幕豆芽| 国产精品久久久福利| 久久99精品九九九久久婷婷| 中文字幕乱码人妻无码久久| 精品国产91久久久久久久a| 中文字幕无码精品亚洲资源网久久 | 亚洲国产成人久久精品99 | 亚洲国产日韩欧美久久| 久久久久中文字幕| 久久精品极品盛宴观看| 99热精品久久只有精品| 久久婷婷国产综合精品| 久久伊人五月丁香狠狠色| 国内精品久久久久久不卡影院 | 2021最新久久久视精品爱| 伊人色综合久久| 狠狠色丁香婷婷综合久久来| 77777亚洲午夜久久多人| 久久综合视频网站| 国产呻吟久久久久久久92| 久久精品aⅴ无码中文字字幕不卡| 国产精品久久久久免费a∨| 国产精品无码久久综合网| 亚洲一本综合久久| 99久久99久久精品免费看蜜桃 | 久久最新免费视频| 精品久久综合1区2区3区激情| 国产精品99久久精品| 久久精品国产69国产精品亚洲| 久久精品蜜芽亚洲国产AV| 无码人妻久久久一区二区三区| 99久久精品免费看国产一区二区三区 | 99久久香蕉国产线看观香| 一级a性色生活片久久无| 中文字幕久久精品 | 久久国产免费直播| 国产精自产拍久久久久久蜜|