.NET 多執行緒

ForTechnology發表於2013-03-26

  受託管的執行緒與 Windows執行緒

必 須要瞭解,執行.NET應用的執行緒實際上仍然是Windows執行緒。但是,當某個執行緒被CLR所知時,我們將它稱為受託管的執行緒。具體來說,由受託管的代 碼建立出來的執行緒就是受託管的執行緒。如果一個執行緒由非託管的程式碼所建立,那麼它就是非託管的執行緒。不過,一旦該執行緒執行了受託管的程式碼它就變成了受託管的 執行緒。

一個受託管的執行緒和非託管的執行緒的區別在於,CLR將建立一個System.Threading.Thread類的例項來代表並操作前者。在內部實現中,CLR將一個包含了所有受託管執行緒的列表儲存在一個叫做ThreadStore地方。

CLR確保每一個受託管的執行緒在任意時刻都在一個AppDomain中執行,但是這並不代表一個執行緒將永遠處在一個AppDomain中,它可以隨著時間的推移轉到其他的AppDomain中。

從安全的角度來看,一個受託管的執行緒的主使用者與底層的非託管執行緒中的Windows主使用者是無關的。
 

  前臺執行緒與後臺執行緒

啟 動了多個執行緒的程式在關閉的時候卻出現了問題,如果程式退出的時候不關閉執行緒,那麼執行緒就會一直的存在,但是大多啟動的執行緒都是區域性變數,不能一一的關 閉,如果呼叫Thread.CurrentThread.Abort()方法關閉主執行緒的話,就會出現ThreadAbortException 異常,因此這樣不行。
後來找到了這個辦法: Thread.IsBackground 設定執行緒為後臺執行緒。
 
msdn對前臺線 程和後臺執行緒的解釋:託管執行緒或者是後臺執行緒,或者是前臺執行緒。後臺執行緒不會使託管執行環境處於活動狀態,除此之外,後臺執行緒與前臺執行緒是一樣的。一旦所 有前臺執行緒在託管程式(其中 .exe 檔案是託管程式集)中被停止,系統將停止所有後臺執行緒並關閉。通過設定 Thread.IsBackground 屬性,可以將一個執行緒指定為後臺執行緒或前臺執行緒。例如,通過將 Thread.IsBackground 設定為 true,就可以將執行緒指定為後臺執行緒。同樣,通過將 IsBackground 設定為 false,就可以將執行緒指定為前臺執行緒。從非託管程式碼進入托管執行環境的所有執行緒都被標記為後臺執行緒。通過建立並啟動新的 Thread 物件而生成的所有執行緒都是前臺執行緒。如果要建立希望用來偵聽某些活動(如套接字連線)的前臺執行緒,則應將 Thread.IsBackground 設定為 true,以便程式可以終止。
所以解決辦法就是在主執行緒初始化的時候,設定:Thread.CurrentThread.IsBackground = true;

這樣,主執行緒就是後臺執行緒,在關閉主程式的時候就會關閉主執行緒,從而關閉所有執行緒。但是這樣的話,就會強制關閉所有正在執行的執行緒,所以在關閉的時候要對執行緒工作的結果儲存。

經常看到名為BeginXXX和EndXXX的方法,他們是做什麼用的

這是.net的一個非同步方法名稱規範
.Net 在設計的時候為非同步程式設計設計了一個非同步程式設計模型(APM),這個模型不僅是使用.NET的開發人員使用,.Net內部也頻繁用到,比如所有的Stream 就有BeginRead,EndRead,Socket,WebRequet,SqlCommand都運用到了這個模式,一般來講,呼叫 BegionXXX的時候,一般會啟動一個非同步過程去執行一個操作,EndEnvoke可以接收這個非同步操作的返回,當然如果非同步操作在 EndEnvoke呼叫的時候還沒有執行完成,EndInvoke會一直等待非同步操作完成或者超時

.Net的非同步程式設計模型(APM)一般包含BeginXXX,EndXXX,IAsyncResult這三個元素,BeginXXX方法都要返回一個IAsyncResult,而EndXXX都需要接收一個IAsyncResult作為引數,他們的函式簽名模式如下

IAsyncResult BeginXXX(...);

EndXXX(IAsyncResult ar);

BeginXXX 和EndXXX中的XXX,一般都對應一個同步的方法,比如FileStream的Read方法是一個同步方法,相應的BeginRead(), EndRead()就是他的非同步版本,HttpRequest有GetResponse來同步接收一個響應,也提供了BeginGetResponse和 EndGetResponse這個非同步版本,而IAsynResult是二者聯絡的紐帶,只有把BeginXXX所返回的IAsyncResult傳給對 應的EndXXX,EndXXX才知道需要去接收哪個BeginXXX發起的非同步操作的返回值。

這個模式在實際使用時稍顯繁瑣,雖然原則上 我們可以隨時呼叫EndInvoke來獲得返回值,並且可以同步多個執行緒,但是大多數情況下當我們不需要同步很多執行緒的時候使用回撥是更好的選擇,在這種 情況下三個元素中的IAsynResult就顯得多餘,我們一不需要用其中的執行緒完結標誌來判斷執行緒是否成功完成(回撥的時候執行緒應該已經完成了),二不 需要他來傳遞資料,因為資料可以寫在任何變數裡,並且回撥時應該已經填充,所以可以看到微軟在新的.Net Framework中已經加強了對回撥事件的支援,這總模型下,典型的回撥程式應該這樣寫

a.DoWork+=new SomeEventHandler(Caculate);
a.CallBack+=new SomeEventHandler(callback);
a.Run();

(注: 我上面講的是普遍的用法,然而BeginXXX,EndXXX僅僅是一種模式,而對這個模式的實現完全取決於使用他的開發人員,具體實現的時候你可以使用 另外一個執行緒來實現非同步,也可能使用硬體的支援來實現非同步,甚至可能根本和非同步沒有關係(儘管幾乎沒有人會這樣做)-----比如直接在Beginxxx 裡直接輸出一個"Helloworld",如果是這種極端的情況,那麼上面說的一切都是廢話,所以上面的探討並不涉及內部實現,只是告訴大家微軟的模式, 和框架中對這個模式的經典實現)

 

非同步和多執行緒有什麼關聯

有一句話總結的很好:多執行緒是實現非同步的一種手段和工具

我們通常把多執行緒和非同步等同起來,實際是一種誤解,在實際實現的時候,非同步有許多種實現方法,我們可以用程式來做非同步,或者使用纖程,或者硬體的一些特性,比如在實現非同步IO的時候,可以有下面兩個方案:

1)可以通過初始化一個子執行緒,然後在子執行緒裡進行IO,而讓主執行緒順利往下執行,當子執行緒執行完畢就回撥

2)也可以根本不使用新執行緒,而使用硬體的支援(現在許多硬體都有自己的處理器),來實現完全的非同步,這是我們只需要將IO請求告知硬體驅動程式,然後迅速返回,然後等著硬體IO就緒通知我們就可以了

實 際上DotNet Framework裡面就有這樣的例子,當我們使用檔案流的時候,如果制定檔案流屬性為同步,則使用BeginRead進行讀取時,就是用一個子執行緒來調 用同步的Read方法,而如果指定其為非同步,則同樣操作時就使用了需要硬體和作業系統支援的所謂IOCP的機制

 

WinForm多執行緒程式設計篇

  

我的多執行緒WinForm程式老是丟擲InvalidOperationException ,怎麼解決?

在WinForm中使用多執行緒時,常常遇到一個問題,當在子執行緒(非UI執行緒)中修改一個控制元件的值:比如修改進度條進度,時會丟擲如下錯誤

Cross-thread operation not valid: Control 'XXX' accessed from a thread other than the thread it was created on.

在VS2005 或者更高版本中,只要不是在控制元件的建立執行緒(一般就是指UI主執行緒)上訪問控制元件的屬性就會丟擲這個錯誤,解決方法就是利用控制元件提供的Invoke和 BeginInvoke把呼叫封送回UI執行緒,也就是讓控制元件屬性修改在UI執行緒上執行,下面列出會報錯的程式碼和他的修改版本

 

ThreadStart threadStart=new ThreadStart(Calculate);//通過ThreadStart委託告訴子執行緒講執行什麼方法
Thread thread=new Thread(threadStart);
thread.Start();
public void Calculate(){
    double Diameter=0.5;
    double result=Diameter*Math.PI;
    CalcFinished(result);//計算完成需要在一個文字框裡顯示
}
public void CalcFinished(double result){
    this.TextBox1.Text=result.ToString();//會丟擲錯誤
}

上面加粗的地方在debug的時候會報錯,最直接的修改方法是修改Calculate這個方法如下

delegate void changeText(double result);

public void Calculate(){
    double Diameter=0.5;
    double result=Diameter*Math.PI;
    this.BeginInvoke(new changeText(CalcFinished),t.Result);//計算完成需要在一個文字框裡顯示
}

這樣就ok了,但是最漂亮的方法是不去修改Calculate,而去修改CalcFinished這個方法,因為程式裡呼叫這個方法的地方可能很多,由於加了是否需要封送的判斷,這樣修改還能提高非跨執行緒呼叫時的效能

 

delegate void changeText(double result);

public void CalcFinished(double result){
    if(this.InvokeRequired){
        this.BeginInvoke(new changeText(CalcFinished),t.Result);
    }
    else{
        this.TextBox1.Text=result.ToString();
    }
}

上面的做法用到了Control的一個屬性InvokeRequired(這個屬性是可以在其他執行緒裡訪問的),這個屬性表明呼叫是否來自另非UI執行緒,如果是,則使用BeginInvoke來呼叫這個函式,否則就直接呼叫,省去執行緒封送的過程

 

Invoke,BeginInvoke幹什麼用的,內部是怎麼實現的?

這兩個方法主要是讓給出的方法在控制元件建立的執行緒上執行

Invoke使用了Win32API的SendMessage,

UnsafeNativeMethods.PostMessage(new HandleRef(this, this.Handle), threadCallbackMessage, IntPtr.Zero, IntPtr.Zero);

BeginInvoke使用了Win32API的PostMessage

UnsafeNativeMethods.PostMessage(new HandleRef(this, this.Handle), threadCallbackMessage, IntPtr.Zero, IntPtr.Zero);

這兩個方法向UI執行緒的訊息佇列中放入一個訊息,當UI執行緒處理這個訊息時,就會在自己的上下文中執行傳入的方法,換句話說凡是使用BeginInvoke和Invoke呼叫的執行緒都是在UI主執行緒中執行的,所以如果這些方法裡涉及一些靜態變數,不用考慮加鎖的問題

每個執行緒都有訊息佇列嗎?

不是,只有建立了窗體物件的執行緒才會有訊息佇列(下面給出關於這一段的描述)

當 一個執行緒第一次被建立時,系統假定執行緒不會被用於任何與使用者相關的任務。這樣可以減少執行緒對系統資源的要求。但是,一旦這個執行緒呼叫一個與圖形使用者介面有 關的函式(例如檢查它的訊息佇列或建立一個視窗),系統就會為該執行緒分配一些另外的資源,以便它能夠執行與使用者介面有關的任務。特別是,系統分配一個T H R E A D I N F O結構,並將這個資料結構與執行緒聯絡起來。

這 個T H R E A D I N F O結構包含一組成員變數,利用這組成員,執行緒可以認為它是在自己獨佔的環境中執行。T H R E A D I N F O是一個內部的、未公開的資料結構,用來指定執行緒的登記訊息佇列(posted-message queue)、傳送訊息佇列( send-message queue)、應答訊息佇列( r e p l y -message queue)、虛擬輸入佇列(virtualized-input queue)、喚醒標誌(wake flag)、以及用來描述執行緒區域性輸入狀態的若干變數。圖2 6 - 1描述了T H R E A D I N F O結構和與之相聯絡的三個執行緒。


為什麼Winform不允許跨執行緒修改UI執行緒控制元件的值

在vs2003下,使用子執行緒呼叫ui執行緒建立的控制元件的屬性是不會有問題的,但是編譯的時候會出現警告,但是vs2005及以上版本就會有這樣的問題,下面是msdn上的描述

"當您在 Visual Studio 偵錯程式中執行程式碼時,如果您從一個執行緒訪問某個 UI 元素,而該執行緒不是建立該 UI 元素時所在的執行緒,則會引發 InvalidOperationException。偵錯程式引發該異常以警告您存在危險的程式設計操作。UI 元素不是執行緒安全的,所以只應在建立它們的執行緒上進行訪問"

從 上面可以看出,這個異常實際是debugger耍的花招,也就是說,如果你直接執行程式的exe檔案,或者利用執行而不除錯(Ctrl+F5)來執行你的 程式,是不會丟擲這樣的異常的.大概ms發現v2003的警告對廣大開發者不起作用,所以用了一個比較狠一點的方法.

不過問題依然存在:既然這樣設計的原因主要是因為控制元件的值非執行緒安全,那麼DotNet framework中非執行緒安全的類千千萬萬,為什麼偏偏跨執行緒修改Control的屬性會有這樣嚴格的限制策略呢?

這個問題我還回答不好,希望博友們能夠予以補充 

有沒有什麼辦法可以簡化WinForm多執行緒的開發

使用backgroundworker,使用這個組建可以避免回撥時的Invoke和BeginInvoke,並且提供了許多豐富的方法和事件

參見.Net多執行緒總結(二)-BackgroundWorker,我在這裡不再贅訴

執行緒池

執行緒池的作用是什麼

作用是減小執行緒建立和銷燬的開銷

建立執行緒涉及使用者模式和核心模式的切換,記憶體分配,dll通知等一系列過程,執行緒銷燬的步驟也是開銷很大的,所以如果應用程式使用了完一個執行緒,我們能把執行緒暫時存放起來,以備下次使用,就可以減小這些開銷

所有程式使用一個共享的執行緒池,還是每個程式使用獨立的執行緒池?

每 個程式都有一個執行緒池,一個Process中只能有一個例項,它在各個應用程式域(AppDomain)是共享的,.Net2.0 中預設執行緒池的大小為工作執行緒25個,IO執行緒1000個,有一個比較普遍的誤解是執行緒池中會有1000個執行緒等著你去取,其實不然, ThreadPool僅僅保留相當少的執行緒,保留的執行緒可以用SetMinThread這個方法來設定,當程式的某個地方需要建立一個執行緒來完成工作時, 而執行緒池中又沒有空閒執行緒時,執行緒池就會負責建立這個執行緒,並且在呼叫完畢後,不會立刻銷燬,而是把他放在池子裡,預備下次使用,同時如果執行緒超過一定時 間沒有被使用,執行緒池將會回收執行緒,所以執行緒池裡存在的執行緒數實際是個動態的過程

為什麼不要手動執行緒池設定最大值?

當我首次看到執行緒池的時候,腦袋裡的第一個念頭就是給他設定一個最大值,然而當我們檢視ThreadPool的SetMaxThreads文件時往往會看到一條警告:不要手動更改執行緒池的大小,這是為什麼呢?

其 實無論FileStream的非同步讀寫,非同步傳送接受Web請求,甚至使用delegate的beginInvoke都會預設呼叫 ThreadPool,也就是說不僅你的程式碼可能使用到執行緒池,框架內部也可能使用到,更改的後果影響就非常大,特別在iis中,一個應用程式池中的所有 WebApplication會共享一個執行緒池,對最大值的設定會帶來很多意想不到的麻煩

執行緒池的執行緒為何要分類?

線 程池有一個方法可以讓我們看到執行緒池中可用的執行緒數量:GetAvaliableThread(out workerThreadCount,out iocompletedThreadCount),對於我來說,第一次看到這個函式的引數時十分困惑,因為我期望這個函式直接返回一個整形,表明還剩多少 執行緒,這個函式居然一次返回了兩個變數.

原來執行緒池裡的執行緒按照公用被分成了兩大類:工作執行緒和IO執行緒,或者IO完成執行緒,前者用於 執行普通的操作,後者專用於非同步IO,比如檔案和網路請求,注意,分類並不說明兩種執行緒本身有差別,執行緒就是執行緒,是一種執行單元,從本質上來講都是一樣 的,執行緒池這樣分類,舉例來說,就好像某施工工地現在有1000把鐵鍬,規定其中25把給後勤部門用,其他都給施工部門,施工部門需要大量使用鐵鍬來挖地 基(例子土了點,不過說明問題還是有效的),後勤部門用鐵鍬也就是剷剷雪,剷剷垃圾,給工人師傅修修臨時住房,所以用量不大,顯然兩個部門的鐵鍬本身沒有 區別,但是這樣的劃分就為管理兩個部門的鐵鍬提供了方便

執行緒池中兩種執行緒分別在什麼情況下被使用,二者工作原理有什麼不同?

下面這個例子直接說明了二者的區別,我們用一個流讀出一個很大的檔案(大一點操作的時間長,便於觀察),然後用另一個輸出流把所讀出的檔案的一部分寫到磁碟上

我們用兩種方法建立輸出流,分別是

建立了一個非同步的流(注意建構函式最後那個true)

FileStream utputfs=new FileStream(writepath, FileMode.Create, FileAccess.Write, FileShare.None,256,true);

建立了一個同步的流

FileStream utputfs = File.OpenWrite(writepath);

 然後在寫檔案期間檢視執行緒池的狀況

 

string readpath = "e:\\RHEL4-U4-i386-AS-disc1.iso";
string writepath = "e:\\kakakak.iso";
byte[] buffer = new byte[90000000];

//FileStream utputfs=new FileStream(writepath, FileMode.Create, FileAccess.Write, FileShare.None,256,true);
//Console.WriteLine("非同步流");
//建立了一個同步的流

FileStream utputfs = File.OpenWrite(writepath);
Console.WriteLine("同步流");

 //然後在寫檔案期間檢視執行緒池的狀況

ShowThreadDetail("初始狀態");

FileStream fs = File.OpenRead(readpath);

fs.BeginRead(buffer, 0, 90000000, delegate(IAsyncResult o)
{

    outputfs.BeginWrite(buffer, 0, buffer.Length,

    delegate(IAsyncResult o1)
    {

        Thread.Sleep(1000);

        ShowThreadDetail("BeginWrite的回撥執行緒");

    }, null);

    Thread.Sleep(500);//this is important cause without this, this Thread and the one used for BeginRead May seem to be same one
},

null);


Console.ReadLine();


public static void ShowThreadDetail(string caller)
{
    int IO;
    int Worker;
    ThreadPool.GetAvailableThreads(out Worker, out IO);
    Console.WriteLine("Worker: {0}; IO: {1}", Worker, IO);
}

輸出結果
 非同步流
 Worker: 500; IO: 1000
 Worker: 500; IO: 999
 同步流
 Worker: 500; IO: 1000
 Worker: 499; IO: 1000
 

這兩個建構函式建立的流都可以使用BeginWrite來非同步寫資料,但是二者行為不同,當使用同步的流進行非同步寫時,通過回撥的輸出我們可以看到,他使用的是工作執行緒,而非IO執行緒,而非同步流使用了IO執行緒而非工作執行緒

其 實當沒有制定非同步屬性的時候,.Net實現非同步IO是用一個子執行緒呼叫fs的同步Write方法來實現的,這時這個子執行緒會一直阻塞直到呼叫完成.這個子 執行緒其實就是執行緒池的一個工作執行緒,所以我們可以看到,同步流的非同步寫回撥中輸出的工作執行緒數少了一,而使用非同步流,在進行非同步寫時,採用了 IOCP方法,簡單說來,就是當BeginWrite執行時,把資訊傳給硬體驅動程式,然後立即往下執行(注意這裡沒有額外的執行緒),而當硬體準備就緒, 就會通知執行緒池,使用一個IO執行緒來讀取

.Net執行緒池有什麼不足

沒有提供方法控制加入執行緒池的執行緒:一旦加入執行緒池,我們沒有辦法掛起,終止這些執行緒,唯一可以做的就是等他自己執行

1)不能為執行緒設定優先順序
2)一個Process中只能有一個例項,它在各個AppDomain是共享的。ThreadPool只提供了靜態方法,不僅我們自己新增進去的WorkItem使用這個Pool,而且.net framework中那些BeginXXX、EndXXX之類的方法都會使用此Pool。
3)所支援的Callback不能有返回值。WaitCallback只能帶一個object型別的引數,沒有任何返回值。
4)不適合用在長期執行某任務的場合。我們常常需要做一個Service來提供不間斷的服務(除非伺服器down掉),但是使用ThreadPool並不合適。

下面是另外一個網友總結的什麼不需要使用執行緒池,我覺得挺好,引用下來
如果您需要使一個任務具有特定的優先順序。
如果您具有可能會長時間執行(並因此阻塞其他任務)的任務。
如果您需要將執行緒放置到單執行緒單元中(所有 ThreadPool 執行緒均處於多執行緒單元中)。
如果您需要與該執行緒關聯的穩定標識。例如,您應使用一個專用執行緒來中止該執行緒、將其掛起或按名稱發現它。

鎖定與同步

CLR怎樣實現lock(obj)鎖定?

從原理上講,lock和Syncronized Attribute都是用Moniter.Enter實現的,比如如下程式碼

object lockobj=new object();
lock(obj){ 
//do things
}

 在編譯時,會被編譯為類似

try{
  Moniter.Enter(obj){
   //do things
  }
}
catch{}
finally{
  Moniter.Exit(obj);
}


而[MethodImpl(MethodImplOptions.Synchronized)]標記為同步的方法會在編譯時被lock(this)語句所環繞
所以我們只簡單探討Moniter.Enter的實現

(注:DotNet並非使用Win32API的CriticalSection來實現Moniter.Enter,不過他為託管物件提供了一個類似的結構叫做Syncblk)

每 個物件例項頭部都有一個指標,這個指標指向的結構,包含了物件的鎖定資訊,當第一次使用Moniter.Enter(obj)時,這個obj物件的鎖定結 構就會被初時化,第二次呼叫Moniter.Enter時,會檢驗這個object的鎖定結構,如果鎖沒有被釋放,則呼叫會阻塞

  WaitHandle是什麼,他和他的派生類怎麼使用

  WaitHandle是Mutex,Semaphore,EventWaitHandler,AutoResetEvent,ManualResetEvent共同的祖先,他們包裝了用於同步的核心物件,也就是說是這些核心物件的託管版本。

  Mutex:類似於一個接力棒,拿到接力棒的執行緒才可以開始跑,當然接力棒一次只屬於一個執行緒(Thread Affinity),如果這個執行緒不釋放接力棒(Mutex.ReleaseMutex),那麼沒辦法,其他所有需要接力棒執行的執行緒都知道能等著看熱鬧

Semaphore: 類似於一個小桶,裡面裝了幾個小球,凡是拿到小球就可以跑,比如指定小桶裡最初有四個小球,那麼開始的四個執行緒就可以直接拿著自己的小球開跑,但是第五個 執行緒一看,小球被拿光了,就只好乖乖的等著有誰放一個小球到小桶裡(Semophore.Release),他才能跑,但是這裡的遊戲規則比較特殊,我們 可以隨意向小桶裡放入小球,也就是說我可以拿走一個小球,放回去倆,甚至一個都不拿,放回去5個,這樣就有五個執行緒可以拿著這些小球執行了.我們可以規定 小桶裡有開始有幾個小球(建構函式的第一個引數),也可以規定最多不能超過多少小球(建構函式的第二個引數) 

什麼是用雙鎖實現Singleton,為什麼要這樣做,雙鎖檢驗是不安全的嗎?

使用雙鎖檢驗技巧來實現單件,來自於Java社群

public static MySingleton Instance{
get{
    if(_instance!=null)}{
        lock(_instance){
            if(s_value==null){
                _instance= new MySingleton();
            }
        }
    }
}


這樣做其實是為了提高效率,比起

public static MySingleton Instance{

get{

lock(_instance){

if(s_value==null){

_instance= new MySingleton();

}

}

前一種方法在instance建立的時候不需要用lock同步,從而增進了效率

在java中這種技巧被證明是不安全的詳細見http://www.cs.umd.edu/~pugh/java/memoryModel/

但是在.Net下,這樣的技巧是成立的,因為.Net使用了改進的記憶體模型

並且在.Net下,我們可以使用LazyInit來實現單件

private static readonly _instance=new MySingleton()

public static MySingleton Instance{

get{return _instance}

}

當第一此使用_instance時,CLR會生成這個物件,以後再訪問這個欄位,將會直接返回

互斥物件(Mutex),訊號量(Semaphore),事件(Event)物件與lock語句的比較

首先這裡所謂的事件物件不是System.Event,而是一種用於同步的核心機制

互斥物件和事件物件屬於核心物件,利用核心物件進行執行緒同步,執行緒必須要在使用者模式和核心模式間切換,所以一般效率很低,但利用互斥物件和事件物件這樣的核心物件,可以在多個程式中的各個執行緒間進行同步。

lock或者Moniter是.net用一個特殊結構實現的,不涉及模式切換,也就是說工作在使用者方式下,同步速度較快,但是不能跨程式同步

什麼時候需要鎖定?

剛剛接觸鎖定的程式設計師往往覺得這個世界非常的危險,每個靜態變數似乎都有可能產生競爭

首先鎖定是解決競爭條件的,也就是多個執行緒同時訪問某個資源,造成意想不到的結果,比如,最簡單的情況,一個計數器,如果兩個執行緒同時加一,後果就是損失了一個計數,但是頻繁的鎖定又可能帶來效能上的消耗,還有最可怕的情況,死鎖

到底什麼情況下我們需要使用鎖,什麼情況下不用呢?

只有共享資源才需要鎖定
首先,只有可以被多執行緒訪問的共享資源才需要考慮鎖定,比如靜態變數,再比如某些快取中的值,屬於執行緒內部的變數不需要鎖定

把鎖定交給資料庫
資料庫除了儲存資料之外,還有一個重要的用途就是同步,資料庫本身用了一套複雜的機制來保證資料的可靠和一致性,這就為我們節省了很多的精力.保證了資料來源頭上的同步,我們多數的精力就可以集中在快取等其他一些資源的同步訪問上了

瞭解你的程式是怎麼執行的
實 際上在web開發中大多數邏輯都是在單個執行緒中展開的,無論asp.net還是php,一個請求都會在一個單獨的執行緒中處理,其中的大部分變數都是屬於這 個執行緒的,根本沒有必要考慮鎖定,當然對於asp.net中的application物件中的資料,我們就要小心一些了

WinForm中凡是使用BeginInvoke和Invoke呼叫的方法也都不需要考慮同步,因為這用這兩個方法呼叫的方法會在UI執行緒中執行,因此實際是同步的,所以如果呼叫的方法中存在某些靜態變數,不需要考慮鎖定

業務邏輯對事務和執行緒安全的要求
這 條是最根本的東西,開發完全執行緒安全的程式是件很費時費力的事情,在電子商務等涉及金融系統的案例中,許多邏輯都必須嚴格的執行緒安全,所以我們不得不犧牲 一些效能,和很多的開發時間來做這方面的工作,而一般的應用中,許多情況下雖然程式有競爭的危險,我們還是可以不使用鎖定,比如有的時候計數器少一多一, 對結果無傷大雅的情況下,我們就可以不用去管他

計算一下衝突的可能性
我 以前曾經談到過,架構不要過設計,其實在這裡也一樣,假如你的全域性快取裡的某個值每天只有幾百或者幾千個訪問,並且訪問時間很短,並且分佈均勻(實際上這 是大多數的情況),那麼衝突的可能性就非常的少,也許每500天才會出現一次或者更長,從7*24小時安全服務的角度來看,也完全符合要求,那麼你還會為 這樣萬分之一的可能性花80%的精力去設計嗎?

請多使用lock,少用Mutex
如 果你一定要使用鎖定,請儘量不要使用核心模組的鎖定機制,比如.net的Mutex,Semaphore,AutoResetEvent, ManuResetEvent,使用這樣的機制涉及到了系統在使用者模式和核心模式間的切換,所以效能差很多,但是他們的優點是可以跨程式同步執行緒,所以應 該清楚的瞭解到他們的不同和適用範圍

Web和IIS

應用程式池,WebApplication,和執行緒池之間有什麼關係

一個應用程式池是一個獨立的程式,擁有一個執行緒池,應用程式池中可以有多個WebApplication,每個執行在一個單獨的AppDomain中,這些WebApplication公用一個執行緒池

不同的AppDomain保證了每個WebApplication的靜態變數不會互相干擾,不同的應用程式池保證了一個網站癱瘓,其他不同程式中的站點還能正常執行

 下圖說明了他們的關係

 

Web頁面怎麼呼叫非同步WebService

把Page的Async屬性設定為true,就可以呼叫非同步的方法,但是這樣呼叫的效果可能並不如我們的相像,請參考Web中使用多執行緒來增強使用者體驗

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/25897606/viewspace-757151/,如需轉載,請註明出處,否則將追究法律責任。

相關文章