在.NET上執行多線程操作要考慮的兩大因素

來源:互聯網
上載者:User

線程管理

線程管理現在變得越來越容易了。在.NET架構中,你可以從線程池中擷取線程。線程池是一個產生線程的工廠,如果它已經產生了一定數量的線程且還沒有被破壞的話,對它的調用會被阻止。但是,如何確保不會有太多的線程在規定時間內運行?畢竟,如果每個線程能夠佔用一個CPU核的100%,那麼有超過CPU核心數量的線程運行,只會導致作業系統啟動線程時間分配,這將導致環境切換和低效率運行。換句話說,同一核上的兩個線程不會以兩倍的時間長度完成,可能需要用兩倍再加10 %左右的時間來完成。與一個線程相比較的話,三個線程在同一核上想佔用100 %的CPU使用率可能會需要3.25——3.5倍的時間長度來完成。我的經驗是,每個核都有多個線程試圖佔用100 %的CPU,但它們都不能達到目標。

所以,要怎樣分配正在啟動並執行線程數量呢?

有一個辦法是線上程之間建立一個共用的旗語對象。線上程開始運行前,它會嘗試調用旗語的WaitOne模式,並在完成後釋放旗語。對CPU的核心數量設定旗語限制,(使用EnvironmentProcessorCount功能限定);這將防止您的系統在同一時間啟動並執行線程數多於核心數量。與此同時,從線程池中拉出線程將確保您不會在同一時間建立過多線程。如果一次建立線程過多,即使他們並沒運行,那也是浪費系統資源。因為每個線程都要消耗資源。使用旗語的一般模式如下所示:

以下為引用的內容:

   static Semaphore threadBlocker;

  static void Execute(object state)

  { threadBlocker.WaitOne(); //Do work threadBlocker.Release(); }

  static void RunThreads()

  { threadBlocker = new Semaphore(0, Environment.ProcessorCount); for (int x = 0; x <= 2000; x++) {ThreadPool.QueueUserWorkItem(new WaitCallback (Execute)); } }

當然還有其他一些辦法可以解決這一問題。前一段時間我想要保持對象的一份〈T〉清單。每個對象代表每個工人組件的完整狀態。執行和完成時,工人組件都會被填入資料。並且他會設定某個功能以指示任務完成。主線程將掃描對象清單,如果啟動並執行線程數量足夠少,就開始運行另一個。說實話,雖然這個方法可行,但對於代碼和調試來說這絕對是個噩夢,所以我一點也不推薦。

資料完整性

總體而言,在資料完整性方面,你要擔心的問題是競爭條件和死結。多個線程試圖在同一時間更新相同的對象就會造成競爭條件,這將招致麻煩。想象一下如果使用下面這段代碼:

以下為引用的內容:

  int x = 5;

  x = x + 10;

現在,如果線程A和線程B在同一時間運行此代碼,將會發生什麼情況?它可以運行得很好?還是會出現什麼問題?如果出現問題,又是些怎樣的問題呢?每個線程都不會一次執行全部語句。因此,我們可以按照以下順序操作:

以下為引用的內容:

  1. Thread A retrieves the value of x (5).

  2. Thread B retrieves the value of x (5).

  3. Thread A assigns x + 10 (15) to x.

  4. Thread B assigns x + 10 (15) to x.

  5. x is now equal to 15.

或者,相同的代碼可以按照不同的順序:

以下為引用的內容:

  1. Thread A retrieves the value of x (5).

  2. Thread A assigned x = 10 (15) to x.

  3. Thread B retrieves the value of x (15).

  4. Thread B assigns x + 10 (25) to x.

    5. x is now equal to 25.

在.NET架構中,最簡單也最常見的解決競爭條件的方法是使用“臨界區”。而在VB.NET中,該語句是“加鎖”,並在C#中是“鎖定”,這兩種語句都是把對象作為參數。其他嘗試鎖定相同對象執行個體使用的臨界區(包括上文所指的)會阻止運行直到鎖定解除,這樣每次就只有一個臨界區運行。我們先前舉例的一段代碼現在看起來是這樣的:

以下為引用的內容:

  int x = 5;

  object lockObject = new object();

  Monitor.Enter(lockObject);

  x = x + 10;

  Monitor.Exit(lockObject);

什麼是監控器可以提供而臨界區做不到的呢?答案是沒有。除非你在解鎖後需要更細粒度的控制權。有些複雜的代碼可能需要鎖定或長或短的一段時間,這都取決於啟動並執行情況,比方一個變數的值。在這種情況下,選擇監控器要比需選擇臨界區更合適。

另一個值得關注的有關資料完整性的問題是死結。當多個線程鎖定資源導致它們都不能夠繼續運行時,就會出現死結。例如:

以下為引用的內容:

  Thread A:
 
  Monitor.Enter(object1);

  Monitor.Enter(object2);

  //Do work

  Monitor.Exit(object1);

  Monitor.Exit(object2);

  Thread B:

  Monitor.Enter(object2);

  Monitor.Enter(object1);

  //Do work

  Monitor.Exit(object1);

  Monitor.Exit(object2);

如果線程A和線程B都調用它們的第一段語句並且同時完成運行,那它們都無法調用它們的第二段語句——這就是一個死結。所以編寫代碼的時候細心,要仔細想清楚怎樣編寫代碼才更有利。死結的發生常見於新手,因為他們過分設定鎖定把它變得太詳細了。如果代碼被嵌套鎖定通常表明需要對編寫的代碼加以認真檢查。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.