C 多執行緒(一)

2021-07-26 20:07:30 字數 4766 閱讀 7454

0 引言

多執行緒技術作為.net中的重要設計元素,受到了大量的關注,在.net歷次的版本更新中被不斷地拓展。隨著多執行緒庫的不斷擴充,充分利用多執行緒工具使其發揮最佳作用變得越來越困難。接下來將寫一些列關於多執行緒的文章,描述一些關於現實世界中多執行緒的思考,以及處理這些多執行緒問題的策略與方法。

1 執行緒概述

乙個cpu每個核在乙個時刻只能執行乙個執行緒,但是乙個pc機卻似乎可以同時執行很多的應用,即使這台pc及只有乙個核。這是因為,cpu對執行緒進行了時間分片,在不同的時間片內執行不同的應用執行緒,由於時間片的持續時間非常短,表面上給人一種所有應用同時進行的感覺。時間分片也是在乙個應用中使用多執行緒的基礎。在使用多執行緒解決問題之前,首先需要明白幾個基本的問題:

也就是說,在使用多執行緒之前需要進行充分的考慮,比如如果管理執行緒的代價比建立執行緒所獲得好處更大,那就要考慮是否應該使用多執行緒。執行緒的使用主要有以下兩種場景:

(1)處理大量的資料;

(2)等待資料。

為了說明多執行緒所帶來的影響,假設cpu只執行我們所建立的執行緒(事實上,一定會執行其他的執行緒)。對於處理大量資料的場景,多執行緒的目的就是最大限度地提高cpu的利用率。事實上,乙個cpu能夠同時處理的執行緒數量是一定的,即cpu的核數量。如果將計算任務平均分配給n個核,那麼完成計算任務的總時間可能是原來的1/n。如果每個執行緒使得乙個核滿負荷運算而不能執行其他任何運算,那麼,多執行緒的效能還要考慮執行緒排程以及執行緒數量的影響。執行緒數量太多的情況下,不一定能達到預期的目的。

資料等待情況下的多執行緒相對複雜一點,此時執行緒的優化取決於你所等待的資料。比如,你所等待的所有資料都是來自網路,而網路的速率是有上限的,此時執行緒的資料速率大於網路速率是沒意義的。比如,在連線資料庫的時候使用執行緒池技術,加入執行緒池的最大執行緒數量是100,那麼同時訪問的執行緒數量不能超過100,不然就有可能引發連線超時的錯誤。

另外乙個需要考慮的關於執行緒的基本問題是執行緒的執行順序。在沒有執行緒控制機制的情況下,執行緒的執行順序是隨機的。下面是一段使用tpl(task parallel library)的多執行緒示例**用於說明執行緒的執行順序:

1 parallel.for(0, 10, (i) =>2);

56 console.writeline("

按任意鍵繼續...");

7 console.readkey();

如前面所述,執行結果顯示執行緒的執行順序是隨機的:

當前執行緒i的值為 9

當前執行緒i的值為

6當前執行緒i的值為

8當前執行緒i的值為

7當前執行緒i的值為

5當前執行緒i的值為

3當前執行緒i的值為

2當前執行緒i的值為

4當前執行緒i的值為

0當前執行緒i的值為

1按任意鍵繼續...

在一些執行緒的執行順序十分重要的場景下,那麼必須對執行緒的的執行順序進行控制。執行緒的控制有許多的工具和機制,後續文章會進行介紹。執行緒的這種競爭性的執行有時會引起bug,並且此類bug比較難定位和診斷,因此在需要考慮執行緒執行順序的場景下,在建立執行緒的時候需要分外小心。

2 資料訪問與執行緒鎖

在一些場景下,某些重要的資料不應該被某些執行緒訪問,防止應為其它執行緒對資料修改而引發bug。控制線程訪問資料的工具有許多,這裡簡單介紹一下最基本的執行緒鎖(lock)工具。

執行緒鎖工具大多在同步的場景下使用,如下面這段**所示:

1

private

static

object _syncroot = new

object

();2

3public

static

int count 45

public

static

void

threadingmethod()

6 //

其他部分

13 console.writeline("

完成執行緒方法

" + localcount + "

的呼叫.");

14 }

如果乙個操作可以同時被多個執行緒訪問,那麼這個操作就叫做執行緒安全操作。執行緒安全操作必須是乙個原子操作或者具有執行緒控制機制的操作。非執行緒安全的操作訪問共享資源的時候,可能引發執行緒bug。在上面一段**中,非執行緒安全**為:

// 關鍵部分

localcount = ++count;

那麼僅僅一行的**為何會是非執行緒安全的呢?在.net中任何單一的操作都會編譯成msil(微軟中間語言,一種偽組合語言),這一過程中可能產生許多中間操作。乙個中間操作又會轉換成許多二進位制彙編操作。而這些操作都可能導致執行緒的錯亂。

在上面的例子中,自增操作會中斷中間操作的執行,從而導致count被重複賦值,結果會偏小。執行緒自增操作是說明這一問題的典型例子,可以通過以下**進一步說明問題:

1

static

void main(string

args)2);

78 console.writeline("

count的值為

" +count);

9 console.writeline("

按任意鍵繼續...");

10console.readkey();

11 }

上述**,我們期望的結果是10,000,但實際執行結果為小於或等於10,000的某個值。

count的值為 9032

按任意鍵繼續...

1

//競爭情景23

public

static

void

threadingmethod()

4

11//

加入暫停使競爭特性更加明顯

12 system.threading.thread.sleep(timespan.frommilliseconds(10

));

13//

其他關鍵**,不受執行緒鎖保護

14 console.writeline("

完成執行緒

" + count + "

的計算.");

15 }

上述**執行的部分結果為:

count的當前值為 94

count的當前值為

95count的當前值為

96完成執行緒

96的計算.

完成執行緒

94的計算.

count的當前值為

97完成執行緒

95的計算.

完成執行緒

97的計算.

count的當前值為

98完成執行緒

98的計算.

count的當前值為

99完成執行緒

99的計算.

count的當前值為

100完成執行緒

100 的計算.

受執行緒鎖保護的count的值按照我們期望的自增進行運算(50次每次自增到100)。但是,未受執行緒鎖保護的關鍵**執行結果出現了混亂,先列印「完成執行緒96...」,然後才列印「完成執行緒94...」。如果將列印操作都放到執行緒鎖內,那麼結果會嚴格按照次序執行。那麼,這裡就涉及乙個問題,到底哪些操作需要執行緒鎖的保護呢?事實上,如果被執行緒鎖定的**可以看成是單執行緒的,也就是說這部分**不會帶來效能的提公升。因此,要盡可能少將**放到執行緒鎖中。

這裡有個小技巧用來減少放到執行緒鎖內的**,那就是將共享的資源另存下來作為區域性變數,這就是最開始的**中localcount的作用。在共享資源count

3 .net中的parallel類

parallel類是tpl(task parallel library,任務並行庫)的一部分,可以在system.threading.task命名空間中找到。執行緒的建立有許多方法,這裡介紹使用parallel進行建立的方法,這種方法相對比較好理解,易於使用,並且parallel類包含豐富而使用特性。典型的應用場景為:

parallel類的典型使用方法為:

parallel.foreach(

ienumerable

sources,

paralleloptions options,

action

body)

這段**給source集合中的每一項建立乙個任務,執行action的主體。

parallelloopstate用於處理特定的條件和異常。paralleloptions用於控制task的執行。這裡,task和執行緒並不是簡單的一一對應的關係,比如,所有建立的task物件可以同時執行而不用考慮執行緒競爭等問題。task這裡先不詳細介紹,現在只要知道,tpl已經解決了一些細節問題,我們可以不用在意。

paralleloptions有三個屬性,使用較多的是maxdegreeofparallelism。該屬性可以和environment.processorcount屬性一起使用從而限制執行適合cpu的核數量。

new

paralleloptions()

parallelloopstate有兩個方法停止執行緒:break()和stop()。stop()會使得雖有的迭代操作中止,break()會停止接下來的迭代操作。這兩個方法都不會使得執行緒馬上停止,如果需要執行緒立即停止,需要使用更加複雜一點的執行緒操作。

此外,執行緒操作一般需要使用try-catch語句塊,使用適當的方法處理異常,應當丟擲異常的地方丟擲,隱藏或者忽略異常會給整個應用帶來很大的隱患。

C 多執行緒(一)

這篇文章其實是對morewindows文章中 1 的乙個問題的思考。下面是他文章的原始碼 include include volatile long g nlogincount 登入次數 unsigned int stdcall fun void ppm 執行緒函式 const dword thre...

C 多執行緒 一

include includeusing namespace std void func int main 輸出 分析 主線程先執行,到了th.join 時阻塞住,建立了乙個新的子執行緒,執行子執行緒,子執行緒執行完,阻塞解除,執行主線程至結束。對以上 進行改動 include includeusi...

C 多執行緒(一) 簡介

多執行緒是為了同步完成多項任務,不是為了提高執行效率,而是為了提高資源使用效率來提高系統的效率。執行緒是在同一時間需要完成多項任務的時候實現的。乙個程式開始執行時,它就是乙個程序,程序所指包括執行中的程式和程式所使用到的記憶體和系統資源。而乙個程序又是由多個執行緒所組成的,執行緒是程式中的乙個執行流...