麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁 > 學院 > 開發設計 > 正文

并發

2019-11-18 18:39:41
字體:
來源:轉載
供稿:網友
在多線程的環境,資源必須得到保護,使得它們不會因為一次允許多于一個線程訪問而受損。

并發和線程是相互糾纏的問題,選擇先學哪個也許很難。本文將先講講并發,它將為后面學習線程準備一些該先了解一下的知識。

術語

并發

并發是這樣一個狀態——許多Task同時啟動。當并發被實現得恰恰當當時,它可能被認為是“harmony”。而實現得糟糕時,就成了”chaos“。

在大部分情況中,所說的Task指的都是線程。然而,Task也可以是進程或者纖程。

兩者之間的分界通常很清楚,而使用合適的技術才是關鍵

Contention

確切的說何為Contention?Contention就是當多于一個Task嘗試著同時訪問那獨獨一個資源時的情況。

如果你是在大家庭長大的孩子,可能這個比喻能很好的解釋它意思。想想家里要是有六個小孩,媽媽把一塊小匹薩放在桌上作為晚餐,會發生什么樣的情況。那就是Contention的含義。

無論何時,只要多個并發的Task需要用讀/寫的方式訪問數據,對數據的訪問都必須得到控制從而保護它的完整性。如果訪問沒有得到控制,兩個或者更多的Task可能會“崩潰”。當其中的一個嘗試著要讀取變量時,另外一個可能要同時對它進行寫入。如果一個Task正在寫,而另外一個正在讀,那個讀的Task可能讀取了部分寫入的數據從而獲得的是損壞了的數據。一般這樣的操作是不會立即導致異常的,而只會在這之后給程序帶來錯誤。

Contention問題經常是在低流量的Implementation中不會出現,因而在開發階段經常是一點問題都沒有。所以在開發的階段應該采用合適的技術和壓力測試。否則就有一些像玩Russian Roulette,問題在開發階段僅僅是偶爾出現但是在部署階段變成了頻繁出現。

資源保護

資源保護是用來阻止由Contention帶來的問題的解決辦法。資源保護的目的是一次僅讓一個Task訪問指定的資源。

解決Contention

無論何時,只要多個線程需要以讀/寫的方式訪問數據,對數據的訪問都必須得到控制從而保護它的完整性。這可能對于不熟悉線程操作的程序員來說intimidating。然而,大部分服務器不需要全局數據。這些程序一般在啟動過程中初始化之后只需要讀取數據。只要沒有寫操作,線程可以沒有任何副作用的讀
取全局數據。

下面講的是解決Contention最常用的辦法。

只讀

最簡單的辦法是只讀。任何簡單類型(整數,字符串,內存)以只讀的方式訪問不需要任何保護。這也可以擴展到諸如TLists等許多復雜類型。只要它們不以讀/寫方式訪問任何全局或者成員變量,類型在只讀方式時都是安全的。

此外,資源可以在任何可能的讀操作之前被改寫。這允許了在讀取它的Task啟動之前,先初始化資源。

Atomic操作

有一種方法是說如果操作是Atomic,資源不要被保護。Atomic操作是這樣一種操作,它太小了以至于不能被計算機處理器分劃開來。因為它尺寸小,從而它不會受到Contentiion的影響因為它將由自身執行而且在執行過程中不會有Task的切換。一般情況下,Atomic操作是被編譯為一條匯編指令的源代碼。

典型的任務諸如讀取或者寫入一個整型或者布爾型變量被認為是Atomic操作,因為它們被編譯為一條Move指令。然而我推薦你絕對不要依賴原子操作,因為某些情況下甚至寫入一個整型或者布爾型變量都能包括多于一個的動作,這要看數據首先是從哪兒讀來的。此外,這還依賴于編譯器內部的奧秘,而這可能會在不告知你的情況下做出改變。依賴源代碼級的Atomic操作將產生未來會有問題的代碼而且可能在多處理器的機器或者別的操作系統上行為非常不同。

我曾經見過一個鐵打不動的Atomic操作。然而一個非常PRominent的未來事件證明了我的觀點,那就是.net。你的代碼首先編譯為IL,然而再編譯為機器碼,可能還是在不同廠商的不同平臺上,你還能確信你的代碼最終還是Atomic操作嗎?

選擇最終還是要看你自己,當然有許多聲音圍繞著對Atomic操作的偏愛和反對。在大部分情況下,依賴原子操作僅僅節省了幾毫秒,以及幾字節的代碼。我強烈推薦不要去使用Atomic操作,因為它們帶來的好處如此至少而liabilities如此之巨大。把所有操作都當作非Atomic操作來對待。

操作系統的支持

許多操作系統對非常基本的線程安全的操作提供了支持。

Windows支持一套稱為Interlocked Function的函數。這些函數的用處非常有限,而且僅僅包括簡單的對整數的操作,諸如步增,步減,加,Swap以及Swap-Compare。

函數的數目和Windows的版本有關,而且可能在低版本的Windows上發生死鎖。在大部分應用程序中,它們提供的性能上的好處非常少。

因為綜合這些,用處有限,不斷變化的支持,可憐的性能優勢因素,建議你用Indy的線程安全的同等物來替代。

Windows還包括對特殊的ipC(進程間通信)對象的支持,這些對象在Delphi有經過包裝的類。這些對象和IPC一樣對線程操作極端有用。

顯式保護

顯式保護包括每個Task都知道一個資源受到了保護而且在訪問這個資源之前采取了顯式的防御步驟。一般這樣的代碼式在被多個Task并發執行的函數之中,或者被封裝到了一個被許多不同位置調用的函數之中作為一個線程安全的封裝。

顯示保護一般要利用資源保護對象。簡單來說,資源保護對象把對資源的訪問限制為了一次一Task。資源保護對象并沒有實際限制對資源的訪問,如果它做到了,它可能必須要知道每一個和所有的資源類型的細節。它就像紅綠燈,而代碼要遵守它并給它提供輸入。每個資源保護對象用不同的機理,不同的輸入,以及不同程度的額外負擔實現了不同種類的紅綠燈。這使得能夠選擇不同的資源保護對象來更好的適應不同類型的資源以及不同的場合。

資源保護對象以不同形式存在,下面就來逐個介紹。

Critical Section

Critical Section可以用來控制對全局資源的訪問。Critical Section是輕量級的并且在VCL中實現于TCriticalSection之中。簡單來說,Critical Section使得多線程程序中的一個線程能夠暫時阻塞所有其他線程嘗試使用同一個Critical Section。Critical Section就像紅綠燈,只當前面的路沒有任何車輛時才變綠。Critical Section可以用來確保一次只有一個線程正在執行那一塊代碼。因此,受到Critical Section保護的那塊代碼應當盡可能的小因為如果使用不當的話它們可能嚴重影響性能。所以,每塊代碼都應當使用它們自己的TCriticalSection,而不是重用全程序共享的TCriticalSection。

要進入Critical Section,使用Enter方法,而Leave方法是用來退出Critical Section的。TCriticalSection還分別有Acquire和Release方法來做與Enter和Leave完全一樣的事情。

假設有一個服務器需要記錄有關登陸了的客戶端的信息,并且要在主線程中顯示這些信息。一個可能的選擇是使用Synchronize。然而使用這個方法在同時有許多客戶登陸時會對連接線程產生性能上的負面影響。取決于服務器的需要,一個更好的選項可能是記錄下信息并且讓主線程用Timer來讀取這些信息。下面的代碼是一個使用了Critical Section的這種技術的例子。

var
GLogCS: TCriticalSection;
GUserLog: TStringList;
procedure TformMain.IdTCPServer1Connect(AThread: TIdPeerThread);
var
s: string;
begin
// Username
s := ReadLn;
GLogCS.Enter; try
GUserLog.Add('User logged in: ' + s);
finally GLogCS.Leave; end;
end;
procedure TformMain.Timer1Timer(Sender: TObject);
begin
GLogCS.Enter; try
listbox1.Items.AddStrings(GUserLog);
GUserLog.Clear;
finally GLogCS.Leave; end;
end;
initialization
GLogCS := TCriticalSection.Create;
GUserLog := TStringList.Create;
finalization
FreeAndNil(GUserLog);
FreeAndNil(GLogCS);
end.

在Connect事件中,用戶名在進入Critical Section之前被讀入一個臨時變量。這樣做是為了避免由阻塞Critical Section帶來的對客戶端可能的減慢。這使得網絡通信能夠在進入Critical Section之前被執行。為了使得性能最佳,Critical Section中的代碼應該越少越好。

Timer1Timer事件在主線程中被主窗體上的一個計時器觸發。計時器的時間間隔可以被縮短來達到更新更加頻繁的目的,但是可能會降低接受連接的速度。如果日志的功能被擴展到了服務器的其他地方,不僅僅是記錄用戶的連接,這更加劇了產生瓶頸的可能。更短促的時間的間隔,更新用戶界面所需的時間就更少。然而許多服務器根本就沒有用戶界面,即便是有也一般是第二位的,比服務客戶端的優先級低得多,從而這是一個很好接受的權衡。

TCritical Section位于SyncObjs Unit中。SyncObjs Unit沒有包括在Delphi 4的標準版中。如果你正在使用Delphi 4的標準版,在Indy的網站上有一個SyncObjs.pas,它雖然沒有實現Borland的SyncObjs.pas中的所有內容,但是實現了TCriticalSection類。

TMultiReadExclusiveWriteSynchronizer(TMREWS)

在前面的例子中,TCriticalSection被用來保護對全局數據的訪問。在那些情況中,全局數據只是一直被更新。然而,如果全局數據有時被只讀的訪問,使用TMultiReadExclusiveWriteSynchronizer可能產生更加有效率的源代碼。TMultiReadExclusiveWriteSynchronizer是一個冗長而又難讀的類。因此它將被簡單的稱作TMREWS。

使用TMREWS的優勢是它允許多線程的并發讀取,同時又與Critical Section一樣允許讀的時候只有一個線程訪問。劣勢是TMREWS用起來要費更高的代價。

不再是Enter/Acquire和Leave/Release,TMREWS有方法BeginRead,EndRead,BeginWrite和EndWrite。

關于TMREWS的特別說明

在Delphi 6之前,TMultiReadExclusiveWriteSynchronizer在從一個read lock變為一個write lock時可能導致死鎖。因此你絕對不應該使用把read lock變成write lock的特性,即便文檔上說是可以這么做的。

如果你需要使用這個功能,有一個折衷的辦法。那就是先釋放read lock然后再獲得write lock。然而一旦你獲得了write lock,然后你必須再次檢查首先迫使你要用一個write lock的條件。如果它仍然存在,執行需要做的事情,否則立刻釋放write lock。

在使用Delphi 6時,TMultiReadExclusiveWriteSynchronizer還是有特別要考慮的地方。所有版本的TMultiReadExclusiveWriteSynchronizer,包括update pack 1和update pack 2中的,都有可能導致死鎖的嚴重問題。沒有已知的解決辦法。Borland知道這個問題的存在,并且已經發布了非官方的補丁并且可能會發布官方的補丁。

Kylix中的TMREWS

Kylix 1和Kylix 2中的TMultiReadExclusiveWriteSynchronizer內部是用Critical Section實現的,并且不會比使用Critical Section有任何優勢。然而,它被包括進來是為了代碼能夠同時用于linux和Windows。在Kylix的未來版本中,TMultiReadExclusiveWriteSynchronizer可能升級為如它在Windows下的表現一樣。

在Critical Section和TMREWS之間選擇

因為TMREWS已經被問題纏上了,我的建議很簡單,就是避免使用它。如果你決定使用它,你應當確信它確實是更好的選擇而且你已經獲得了一個不再產生死鎖行為的打過補丁的版本。

在大部分情況下,對TCriticalSection的恰當應用可以產生幾乎一樣快的效果,而且在某些情況下是更快。學會在必要的地方優化你的TCriticalSection,因為對TCriticalSection的不恰當使用將對性能產生嚴重的負面影響。

任何資源保護問題的關鍵都是使用多個資源Controller,并且讓加鎖區域盡可能的小。當能做到這些時,總是應當使用Critical Section,因為它是輕量的而且比TMREWS更快。總的來說,除非你能夠明顯的判斷TMREWS的用處,總是使用Critical Section。

TMREWS類在以下條件都滿足時性能更好:

1、訪問包括讀和寫
2、讀是主要的
3、加鎖的時間必須被擴展開來維護,不能被分解為更小的塊。
4、TMREWS類被合適地打上了補丁,并且已知工作正常了。

性能比較

如前面提到過地,Critical Section更加輕量因而速度更快。Critical Section是由操作系統實現的。操作系統是使用非常快速和精簡的匯編代碼來實現它們的。

TMREWS類更加復雜因而會帶來更多的額外負擔。它必須管理請求者的列表來合理地管理雙狀態的加鎖機制。

為了展示這些區別,創建了一個名為ConcurrencySpeed.dpr的示例項目。它執行了三個如下的簡單的肚量:
1、TCriticalSection – Enter 和 Leave
2、TMREWS – BeginRead 和 EndRead
3、TMREWS – BeginWrite 和 EndWrite

測試是在一個計數的循環中運行它們一定次數。為了測試的目的,缺省是100,000次。在我的測試中,產生了如下的結果(毫秒計):
TCriticalSection:20
TMREWS(Read Lock):150
TMREWS(Write Lock):401

自然這些測量是和機器有關的。然而,這兒它們之間的差別才是重要的,而不是確切的數字。可以明顯的看出TMREWS的read lock比Critical Section慢7.5倍,而write lock要慢20倍。

還應該注意此時Critical Section只有一種結果,而TMREWS的性能在并發使用時還會下降。這兒執行的測試只是簡單的在一個循環之中,沒有其他的請求者在進行請求或者有已經存在鎖需要TMREWS來對付。在實際的情況之中,TMREWS可能比這兒顯示的數字還要慢。

Mutex

Mutex和Critical Section的功能幾乎一致。Mutex的不同之處在于它是一個有更多功能的增強版Critical Section,當然額外負擔也更多。

Mutex有像能夠命名,賦予安全屬性,進程間訪問這樣的額外功能。

Mutex可以在線程之間使用,但是很少這么用。Mutex被設計用于進程間通信,而且一般也是這么用的。

Semaphore

Semaphore類似于Mutex,但是不僅僅是一個Entrant,它允許多個Entrant。Entrant的數量可以在Semaphore創建時指定。

假想一下Mutex是一個正在守衛銀行現鈔提款機(ATM)的安全警衛。一次僅一人可以使用它,但是安全警衛正在保衛機器不讓一隊人同時使用它。

如果安裝了4臺ATM,Semaphore可能就能派上用場。在這種情況下,安全警衛可能允許一次4人進入并使用ATM,但是一次不能多于4人。

Event

Event是用于線程或者進程間來通知某事已經發生的信號。Event可以在某事被完成或者需要干預時用來通知其他Task。

線程安全的類

線程安全的類是經過特別設計用來保護特定類型資源的類。每個線程安全的類都實現了一種類型的資源,并且對資源是什么和如何用它都有良好的認知。

線程安全的類可以簡單如線程安全的整數,也可以復雜如線程安全的數據庫。線程安全的類內部使用線程安全對象來完成它們的功能。

Compartmentalization

Compartmentalization是分離數據并把它賦給單一Task使用的過程。對于服務器來說,Compartmentalization經常是自然而然的,因為每個客戶端都能由專門的線程來處理。

當Compartmentalization不是天然的時,應當考量考量,看看是不是能夠這么做。Compartmentalization經常可以通過拷貝全局數據,對數據進行處理,然后把結果返回給全局區域來達到。通過使用Compartmentalization,數據的加鎖僅僅發生在初始化和任務結束或者批量更新之時。


上一篇:為何選擇Indy?

下一篇:組件制作之四(定制外觀)

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
學習交流
熱門圖片

新聞熱點

疑難解答

圖片精選

網友關注

主站蜘蛛池模板: 成人羞羞在线观看网站 | 在线播放免费播放av片 | 黄色片在线播放 | 久色精品视频 | 国产88久久久国产精品免费二区 | 欧美zoofilia杂交videos | 羞羞网站在线观看入口免费 | 九九热视频这里只有精品 | 成人黄色短视频在线观看 | 在线成人一区二区 | 久久蜜桃精品一区二区三区综合网 | 在线观看国产网站 | 日本a级一区 | 欧美性生活久久 | 亚洲第一成人在线 | 欧美精品激情视频 | 久久综合精品视频 | 91av日韩| 亚洲成人精品久久久 | 粉嫩蜜桃麻豆免费大片 | 男女羞羞在线观看 | 国产免费传媒av片在线 | 久草导航 | 国产一区二区三区四区五区在线 | 国产精品久久久久无码av | 国产精品一区二区在线 | 黄色大片www | 欧美日韩网站在线观看 | 国产一区精品在线观看 | 黄色片网站在线看 | 免费高潮在线国 | av视在线 | 污污网站入口 | 一级黄色在线免费观看 | 激情福利视频 | 欧美一级做a | 把娇妻调教成暴露狂 | 亚洲骚综合 | 欧美色另类| 久久999久久 | 在线视频 亚洲 |