<a href="http://kb.cnblogs.com/page/68545/#basics">基礎篇</a>
<a href="http://kb.cnblogs.com/page/68545/#b1">怎樣建立一個線程</a>
<a href="http://kb.cnblogs.com/page/68545/#b4">受托管的線程與 Windows線程</a>
<a href="http://kb.cnblogs.com/page/68545/#b5">前台線程與背景線程</a>
<a href="http://kb.cnblogs.com/page/68545/#b2">名為BeginXXX和EndXXX的方法是做什麼用的</a>
<a href="http://kb.cnblogs.com/page/68545/#b3">異步和多線程有什麼關聯</a>
<a href="http://kb.cnblogs.com/page/68545/#wm">WinForm多線程程式設計篇</a>
<a href="http://kb.cnblogs.com/page/68545/#wm1">我的多線程WinForm程式老是抛出InvalidOperationException ,怎麼解決?</a>
<a href="http://kb.cnblogs.com/page/68545/#wm2">Invoke,BeginInvoke幹什麼用的,内部是怎麼實作的</a>
<a href="http://kb.cnblogs.com/page/68545/#wm3">每個線程都有消息隊列嗎?</a>
<a href="http://kb.cnblogs.com/page/68545/#wm4">為什麼Winform不允許跨線程修改UI線程控件的值</a>
<a href="http://kb.cnblogs.com/page/68545/#wm5">有沒有什麼辦法可以簡化WinForm多線程的開發</a>
<a href="http://kb.cnblogs.com/page/68545/#tp">線程池</a>
<a href="http://kb.cnblogs.com/page/68545/#tp1">線程池的作用是什麼?</a>
<a href="http://kb.cnblogs.com/page/68545/#tp2">所有程序使用一個共享的線程池,還是每個程序使用獨立的線程池?</a>
<a href="http://kb.cnblogs.com/page/68545/#tp3">為什麼不要手動線程池設定最大值?</a>
<a href="http://kb.cnblogs.com/page/68545/#tp4">.Net線程池有什麼不足?</a>
<a href="http://kb.cnblogs.com/page/68545/#lock">同步</a>
<a href="http://kb.cnblogs.com/page/68545/#lock1">CLR怎樣實作lock(obj)鎖定?</a>
<a href="http://kb.cnblogs.com/page/68545/#lock2">WaitHandle是什麼,他和他的派生類怎麼使用</a>
<a href="http://kb.cnblogs.com/page/68545/#lock3">什麼是用雙鎖實作Singleton,為什麼要這樣做,為什麼有人說雙鎖檢驗是不安全的</a>
<a href="http://kb.cnblogs.com/page/68545/#lock4">互斥對象(Mutex)、事件(Event)對象與lock語句的比較</a>
<a href="http://kb.cnblogs.com/page/68545/#nl">什麼時候需要鎖定</a>
<a href="http://kb.cnblogs.com/page/68545/#nl1">隻有共享資源才需要鎖定</a>
<a href="http://kb.cnblogs.com/page/68545/#nl2">把鎖定交給資料庫</a>
<a href="http://kb.cnblogs.com/page/68545/#nl3">了解你的程式是怎麼運作的</a>
<a href="http://kb.cnblogs.com/page/68545/#nl4">業務邏輯對事務和線程安全的要求</a>
<a href="http://kb.cnblogs.com/page/68545/#nl5">計算一下沖突的可能性</a>
<a href="http://kb.cnblogs.com/page/68545/#nl6">請多使用lock,少用Mutex</a>
<a href="http://kb.cnblogs.com/page/68545/#iis">Web和IIS</a>
<a href="http://kb.cnblogs.com/page/68545/#iis1">應用程式池,WebApplication,和線程池之間有什麼關系</a>
<a href="http://kb.cnblogs.com/page/68545/#iis2">Web頁面怎麼調用異步WebService</a>
一)使用Thread類

ThreadStart threadStart=new ThreadStart(Calculate);//通過ThreadStart委托告訴子線程講執行什麼方法,這裡執行一個計算圓周長的方法

Thread thread=new Thread(threadStart);

thread.Start(); //啟動新線程

public void Calculate(){
double Diameter=0.5;
Console.Write("The perimeter Of Circle with a Diameter of {0} is {1}"Diameter,Diameter*Math.PI);
}
二)使用Delegate.BeginInvoke
delegate double CalculateMethod(double Diameter); //申明一個委托,表明需要在子線程上執行的方法的函數簽名
static CalculateMethod calcMethod = new CalculateMethod(Calculate);//把委托和具體的方法關聯起來
static void Main(string[] args)
{
//此處開始異步執行,并且可以給出一個回調函數(如果不需要執行什麼後續操作也可以不使用回調)
calcMethod.BeginInvoke(5, new AsyncCallback(TaskFinished), null);
Console.ReadLine();
//線程調用的函數,給出直徑作為參數,計算周長
public static double Calculate(double Diameter)
return Diameter * Math.PI;
//線程完成之後回調的函數
public static void TaskFinished(IAsyncResult result)
double re = 0;
re = calcMethod.EndInvoke(result);
Console.WriteLine(re);
三)使用ThreadPool.QueueworkItem

WaitCallback w = new WaitCallback(Calculate);

//下面啟動四個線程,計算四個直徑下的圓周長

ThreadPool.QueueUserWorkItem(w, 1.0);

ThreadPool.QueueUserWorkItem(w, 2.0);

ThreadPool.QueueUserWorkItem(w, 3.0);

ThreadPool.QueueUserWorkItem(w, 4.0);

public static void Calculate(double Diameter)
return Diameter * Math.PI;
下面兩條來自于http://www.cnblogs.com/tonyman/archive/2007/09/13/891912.html
必須要了解,執行.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;
這樣,主線程就是背景線程,在關閉主程式的時候就會關閉主線程,進而關閉所有線程。但是這樣的話,就會強制關閉所有正在執行的線程,是以在關閉的時候要對線程工作的結果儲存。
這是.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中使用多線程時,常常遇到一個問題,當在子線程(非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.Start();
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);

this.BeginInvoke(new changeText(CalcFinished),t.Result);//計算完成需要在一個文本框裡顯示
}
這樣就ok了,但是最漂亮的方法是不去修改Calculate,而去修改CalcFinished這個方法,因為程式裡調用這個方法的地方可能很多,由于加了是否需要封送的判斷,這樣修改還能提高非跨線程調用時的性能


if(this.InvokeRequired){
this.BeginInvoke(new changeText(CalcFinished),t.Result);
}
else{
this.TextBox1.Text=result.ToString();
上面的做法用到了Control的一個屬性InvokeRequired(這個屬性是可以在其他線程裡通路的),這個屬性表明調用是否來自另非UI線程,如果是,則使用BeginInvoke來調用這個函數,否則就直接調用,省去線程封送的過程
這兩個方法主要是讓給出的方法在控件建立的線程上執行
Invoke使用了Win32API的SendMessage,
UnsafeNativeMethods.PostMessage(new HandleRef(this, this.Handle), threadCallbackMessage, IntPtr.Zero, IntPtr.Zero);
BeginInvoke使用了Win32API的PostMessage
這兩個方法向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結構和與之相聯系的三個線程。
在vs2003下,使用子線程調用ui線程建立的控件的屬性是不會有問題的,但是編譯的時候會出現警告,但是vs2005及以上版本就會有這樣的問題,下面是msdn上的描述
"當您在 Visual Studio 調試器中運作代碼時,如果您從一個線程通路某個 UI 元素,而該線程不是建立該 UI 元素時所在的線程,則會引發 InvalidOperationException。調試器引發該異常以警告您存在危險的程式設計操作。UI 元素不是線程安全的,是以隻應在建立它們的線程上進行通路"
從上面可以看出,這個異常實際是debugger耍的花招,也就是說,如果你直接運作程式的exe檔案,或者利用運作而不調試(Ctrl+F5)來 運作你的程式,是不會抛出這樣的異常的.大概ms發現v2003的警告對廣大開發者不起作用,是以用了一個比較狠一點的方法.
不過問題依然存在:既然這樣設計的原因主要是因為控件的值非線程安全,那麼DotNet framework中非線程安全的類千千萬萬,為什麼偏偏跨線程修改Control的屬性會有這樣嚴格的限制政策呢?
這個問題我還回答不好,希望博友們能夠予以補充
使用backgroundworker,使用這個組建可以避免回調時的Invoke和BeginInvoke,并且提供了許多豐富的方法和事件
作用是減小線程建立和銷毀的開銷
建立線程涉及使用者模式和核心模式的切換,記憶體配置設定,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 outputfs=new FileStream(writepath, FileMode.Create, FileAccess.Write, FileShare.None,256,true);
建立了一個同步的流
FileStream outputfs = File.OpenWrite(writepath);
然後在寫檔案期間檢視線程池的狀況

string readpath = "e:\\RHEL4-U4-i386-AS-disc1.iso";

string writepath = "e:\\kakakak.iso";

byte[] buffer = new byte[90000000];


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

//Console.WriteLine("異步流");

//建立了一個同步的流


FileStream outputfs = 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);



public static void ShowThreadDetail(string caller)
int IO;
int Worker;
ThreadPool.GetAvailableThreads(out Worker, out IO);
Console.WriteLine("Worker: {0}; IO: {1}", Worker, IO);
這兩個構造函數建立的流都可以使用BeginWrite來異步寫資料,但是二者行為不同,當使用同步的流進行異步寫時,通過回調的輸出我們可以看到,他使用的是工作線程,而非IO線程,而異步流使用了IO線程而非工作線程
其實當沒有制定異步屬性的時候,.Net實作異步IO是用一個子線程調用fs的同步Write方法來實作的,這時這個子線程會一直阻塞直到調用完 成.這個子線程其實就是線程池的一個工作線程,是以我們可以看到,同步流的異步寫回調中輸出的工作線程數少了一,而使用異步流,在進行異步寫時,采用了 IOCP方法,簡單說來,就是當BeginWrite執行時,把資訊傳給硬體驅動程式,然後立即往下執行(注意這裡沒有額外的線程),而當硬體準備就緒, 就會通知線程池,使用一個IO線程來讀取
沒有提供方法控制加入線程池的線程:一旦加入線程池,我們沒有辦法挂起,終止這些線程,唯一可以做的就是等他自己執行
1)不能為線程設定優先級
2)一個Process中隻能有一個執行個體,它在各個AppDomain是共享的。ThreadPool隻提供了靜态方法,不僅我們自己添加進去的WorkItem使用這個Pool,而且.net framework中那些BeginXXX、EndXXX之類的方法都會使用此Pool。
3)所支援的Callback不能有傳回值。WaitCallback隻能帶一個object類型的參數,沒有任何傳回值。
4)不适合用在長期執行某任務的場合。我們常常需要做一個Service來提供不間斷的服務(除非伺服器down掉),但是使用ThreadPool并不合适。
下面是另外一個網友總結的什麼不需要使用線程池,我覺得挺好,引用下來:
如果您需要使一個任務具有特定的優先級。
如果您具有可能會長時間運作(并是以阻塞其他任務)的任務。
如果您需要将線程放置到單線程單元中(所有 ThreadPool 線程均處于多線程單元中)。
如果您需要與該線程關聯的穩定辨別。例如,您應使用一個專用線程來中止該線程、将其挂起或按名稱發現它。
從原理上講,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是Mutex,Semaphore,EventWaitHandler,AutoResetEvent,ManualResetEvent共同的祖先,他們包裝了用于同步的核心對象,也就是說是這些核心對象的托管版本。
Mutex:類似于一個接力棒,拿到接力棒的線程才可以開始跑,當然接力棒一次隻屬于一個線程(Thread Affinity),如果這個線程不釋放接力棒(Mutex.ReleaseMutex),那麼沒辦法,其他所有需要接力棒運作的線程都知道能等着看熱鬧
Semaphore:類似于一個小桶,裡面裝了幾個小球,凡是拿到小球就可以跑,比如指定小桶裡最初有四個小球,那麼開始的四個線程就可以直接拿着 自己的小球開跑,但是第五個線程一看,小球被拿光了,就隻好乖乖的等着有誰放一個小球到小桶裡(Semophore.Release),他才能跑,但是這 裡的遊戲規則比較特殊,我們可以随意向小桶裡放入小球,也就是說我可以拿走一個小球,放回去倆,甚至一個都不拿,放回去5個,這樣就有五個線程可以拿着這 些小球運作了.我們可以規定小桶裡有開始有幾個小球(構造函數的第一個參數),也可以規定最多不能超過多少小球(構造函數的第二個參數)
使用雙鎖檢驗技巧來實作單件,來自于Java社群
public static MySingleton Instance{
get{
if(_instance!=null)}{
lock(_instance){
if(s_value==null){
_instance= new MySingleton();
}
}
}

這樣做其實是為了提高效率,比起
public static MySingleton Instance{
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()
get{return _instance}
當第一此使用_instance時,CLR會生成這個對象,以後再通路這個字段,将會直接傳回
首先這裡所謂的事件對象不是System.Event,而是一種用于同步的核心機制
互斥對象和事件對象屬于核心對象,利用核心對象進行線程同步,線程必須要在使用者模式和核心模式間切換,是以一般效率很低,但利用互斥對象和事件對象這樣的核心對象,可以在多個程序中的各個線程間進行同步。
lock或者Moniter是.net用一個特殊結構實作的,不涉及模式切換,也就是說工作在使用者方式下,同步速度較快,但是不能跨程序同步
剛剛接觸鎖定的程式員往往覺得這個世界非常的危險,每個靜态變量似乎都有可能産生競争
首先鎖定是解決競争條件的,也就是多個線程同時通路某個資源,造成意想不到的結果,比如,最簡單的情況,一個計數器,如果兩個線程同時加一,後果就是損失了一個計數,但是頻繁的鎖定又可能帶來性能上的消耗,還有最可怕的情況,死鎖
到底什麼情況下我們需要使用鎖,什麼情況下不用呢?
首先,隻有可以被多線程通路的共享資源才需要考慮鎖定,比如靜态變量,再比如某些緩存中的值,屬于線程内部的變量不需要鎖定
資料庫除了存儲資料之外,還有一個重要的用途就是同步,資料庫本身用了一套複雜的機制來保證資料的可靠和一緻性,這就為我們節省了很多的精力.保證了資料源頭上的同步,我們多數的精力就可以集中在緩存等其他一些資源的同步通路上了
實際上在web開發中大多數邏輯都是在單個線程中展開的,無論asp.net還是php,一個請求都會在一個單獨的線程中處理,其中的大部分變量都是屬于 這個線程的,根本沒有必要考慮鎖定,當然對于asp.net中的application對象中的資料,我們就要小心一些了
WinForm中凡是使用BeginInvoke和Invoke調用的方法也都不需要考慮同步,因為這用這兩個方法調用的方法會在UI線程中執行,是以實際是同步的,是以如果調用的方法中存在某些靜态變量,不需要考慮鎖定
這條是最根本的東西,開發完全線程安全的程式是件很費時費力的事情,在電子商務等涉及金融系統的案例中,許多邏輯都必須嚴格的線程安全,是以我們不得不犧 牲一些性能,和很多的開發時間來做這方面的工作,而一般的應用中,許多情況下雖然程式有競争的危險,我們還是可以不使用鎖定,比如有的時候計數器少一多 一,對結果無傷大雅的情況下,我們就可以不用去管他
我以前曾經談到過,架構不要過設計,其實在這裡也一樣,假如你的全局緩存裡的某個值每天隻有幾百或者幾千個通路,并且通路時間很短,并且分布均勻(實際上 這是大多數的情況),那麼沖突的可能性就非常的少,也許每500天才會出現一次或者更長,從7*24小時安全服務的角度來看,也完全符合要求,那麼你還會 為這樣萬分之一的可能性花80%的精力去設計嗎?
如果你一定要使用鎖定,請盡量不要使用核心子產品的鎖定機制,比如.net的 Mutex,Semaphore,AutoResetEvent,ManuResetEvent,使用這樣的機制涉及到了系統在使用者模式和核心模式間的切 換,是以性能差很多,但是他們的優點是可以跨程序同步線程,是以應該清楚的了解到他們的不同和适用範圍
一個應用程式池是一個獨立的程序,擁有一個線程池,應用程式池中可以有多個WebApplication,每個運作在一個單獨的AppDomain中,這些WebApplication公用一個線程池
不同的AppDomain保證了每個WebApplication的靜态變量不會互相幹擾,不同的應用程式池保證了一個網站癱瘓,其他不同程序中的站點還能正常運作
下圖說明了他們的關系
推薦文章
http://alang79.blogdriver.com/alang79/456761.html
<a href="http://www.west-wind.com/presentations/howaspnetworks/howaspnetworks.asp">A low-level Look at the ASP.NET Architecture</a>
參考資料
這本書裡對核心對象的描述比較詳盡
<.Net架構程式設計>和上面一本一樣也是大牛Jeffery Richard的作品
本文轉自快樂就好部落格園部落格,原文連結:http://www.cnblogs.com/happyday56/p/3824769.html,如需轉載請自行聯系原作者