C 基礎 多執行緒

2022-03-16 04:20:27 字數 4002 閱讀 1845

一.微軟早期作業系統中的問題

在早期的作業系統中,應用程式都是在同乙個位址空間中執行的,每個程式的資料其它程式都是可見的,並且因為早期cpu是單核心 的所以所有的執行都是線性的。這就引出兩個問題:

第一:資料的安全性問題,如果有乙個惡意程式被載入到記憶體當中,意味著它可以檢視所有程式的資料,諸如密碼,帳號之類,非常的不安全。

第二:如果有乙個程式出現死迴圈,或者出現錯誤 ,意味著其它程式沒有機會執行,而對於使用者來說,只能關機或重啟,使用者體驗非常之差。

對於兩個問題微軟重新設計了作業系統的核心,

程序的出現

每個載入到記憶體的程式都有自已的完整記憶體空間,這個記憶體空間是虛擬的,只對本程序可見,其它程式不可見,而這個記憶體空間部分通過某種規則對映到物理記憶體上。這就保證了使用者資料的安全。這就相當於抽像出來了記憶體。

2.執行緒的出現

執行緒主要是為了抽像cpu資源,執行緒給程式的感覺是有乙個cpu在一直執行我的**,而真實的情況是,我們的機器沒有那麼多cpu,只是作業系統虛擬出來了cpu來執行**,作業系統通過論詢的方式,給每個執行緒一小段時間在cpu上執行,然後訊速切換到下乙個執行緒,這樣每個執行緒都有機會執行,執行緒切換相對於使用者的感覺很短,所以對使用者來說,程式就像一直在執行一樣,這樣乙個程式出現死迴圈,不會影響到其它程式。

二.普通執行緒及問題,計算限制的非同步操作

在早期的機器上cpu都是單核的,所以執行緒的多少對執行效率不會出現特別明顯的提公升,而現在cpu大多已經多核了,所以很有必要運用多執行緒技術來提高執行效率,而且一些桌面程式的主線程都在忙活介面時,再建立一些執行緒出來來計算,或者寫資料,從而不阻塞主線程,給使用者的體驗也是不錯的。

建立乙個執行緒可以用以下**:

static void main(string args)

static void otherthread(object sta)

這種方式建立的執行緒預設是前台執行緒,乙個程序的結束是所有前台執行緒的結束才會關閉,可以用t.isbackground = true;來設定這個執行緒變為乙個後台執行緒。

在clr中線程是對作業系統執行緒的乙個包裝,現在來講,乙個clr執行緒和乙個作業系統執行緒是一對一的關係,沒準在以後的版本中會變。

建立執行緒是乙個有點浪費資源的事情,因為建立乙個執行緒要給他在記憶體中分配空間以容納下面這些資料:

1.thread kernal object,執行緒核心物件,這個是儲存在核心模式的記憶體中的,主要有一些執行緒的屬性,還有執行緒的暫存器的值。

2.執行緒環境塊,儲存在使用者模式記憶體中,包含執行緒的異常鍊錶,執行緒每進入乙個try,就會在鍊錶中插入一項,每退出乙個try會從鍊錶中刪除一項,另外環境塊還包含執行緒的本地儲存的一些資訊。

3.使用者模式棧,在使用者模式記憶體中,包含執行緒執行中的本地變數和引數。函式呼叫的返回位址等。

4.核心模式棧,在核心模式的記憶體中,執行緒有時候會呼叫核心模式的函式,這時函式的引數會被複製到記憶體模式的棧中並驗證引數的正確性,等函式執行完後再返回到使用者模式棧中。

1.首選要把現在cpu執行執行緒的值放到執行緒核心物件中。

2.作業系統通過演算法,找出要切換到的執行緒。

3.把要切換到的執行緒的cpu暫存器的值從目標執行緒核心物件中載入到暫存器中

整個切換過程cpu的所有執行,都沒有對我們的生產效率起到作用,也就是沒有做我們真正希望它做的動作。如果執行緒屬於另乙個程序,這種浪費會更嚴重,因為作業系統還要把記憶體對映空間切換到目標執行緒所在的程序位址空間。

所以基於以上的原因,並不是我們建立的執行緒越多越好,而是應該盡可能建立有效的執行緒,用最少的執行緒做更多的事,而不是拼命建立執行緒,讓cpu把時間都浪費在上下文切換這種事兒上。

有一種機制比較好,就是我們建立一些執行緒,快取起來,平常的時候讓這些執行緒休眠,而不占用cpu時間讓他切來切去,在有工作的時候讓他執行,沒有工作的時候就讓他休息。

.net framework為我們提供了這樣的支援 ,這就是執行緒池技術。

我們可以用:threadpool.queueuserworkitem(new waitcallback(otherthread), sta);來讓執行緒池中的執行緒來執行我們的**。

執行緒池中的執行緒預設都是後台執行緒。

還有一種執行執行緒池執行緒的方式就是,構造乙個task物件。如下:

taskt = new task(otherthread);

t.start();

task會比queueuserworkitem占用的記憶體多一點,但task可以有返回值,或者後繼任務,靈活性比queueuserworkitem高一點。

另外還可以用cancellationtokensource讓執行緒支援取消。

三.io限制的非同步操作

主要是apm(非同步程式設計模型)

四.應用開發中的非同步應用,mvc ,async,await

五執行緒同步

使用者模式的執行緒同步構造。

優點是比核心模式的同步構造快,因為核心模式構造還要切換到核心模式。

缺點就是作業系統不知道,所以一但阻塞了執行緒還是會被排程,浪費cpu時間。

所以使用建議是,在很快就完成的**上用使用者模式構造,在比較慢的時候用核心 模式構造。

分兩種:

一種是易失構造:在簡單資料型別的變數上實現原子性的讀或寫。

一種是互斥構造:在簡單資料型別的變數上實現原子性的讀和寫。

易失構造用的是

thread.volatileread(ref byte address)

thread.volatilewrite(ref byte address,byte value)

內建的關鏈字是volatile 

讀操作實現的是:不快取資料,直接從raw中讀取,並且其它的變數在本變數讀取之後讀取。

寫操作實現的是:不快取資料,直接寫入raw中,並且其它的變數在本變數寫之後全部已寫入。

互斥構造用的是:

interlocked.exchange(ref double location1,double value)

同樣可以保證寫之前的變數已寫入,讀之後的變數還沒讀之外,還可以保證本次的讀和寫也是原子性的。

另外可以用它構造自懸鎖。

核心模式的執行緒同步構造。

核心 模式的同步構造分為兩種,一種是事件,一種是訊號量

核心 模式的同步構造都是實現自waithandle,所以都具有這個類所有的方法。

事件同步構造其實是在核心中維護乙個bool的變數,當變數是true時,執行緒不阻塞,當變數為false時,阻塞。

有兩個類的實現,兩個類都實現自eventwaithandle類,這個類有兩個方法,乙個是set,將核心 變數設為true不阻塞執行緒,另乙個是reset,使執行緒阻塞。

manualresetevent,手動重置事件,特點是:當用set讓執行緒不阻塞時,所有執行緒全部不阻塞,全部可執行,需要再手動設定成阻塞狀態。

autoresetevent,手動重置事件,特點是,當用set讓執行緒不阻塞時,只有乙個執行緒變成不阻塞,之後事件雙變成false,以阻塞其它執行緒。

訊號量semaphore,相當於乙個計數器,在核心中維護的是乙個int型別的變數,當計數器為0時阻塞執行緒,當大於0時不阻塞,每次waitone時,計數器自動減一,每次乙個執行緒realse時,計數器自動加1,在構造型別的時候,可以指定最小和最大的計數器。

.net中的lock關鍵字以及monitor,及存在的問題和建議。

monitor相當於乙個混合同步構造,內部即用了使用者模式構造也用了核心模式構造,結合了兩個的優點,使用者模式的快,和核心模式的不點用cpu。

.net專門用乙個關鍵字來構造monitor同步構造:lock

bool isenter = false;

tryfinally

}而lock是不用傳this的,那他是怎麼實現的呢?

每個物件在堆的儲存中都是乙個同步塊索引,這個索引指向同步塊陣列中的乙個值,當本物件沒有同步時這個索引為-1,當需要同步時,這個索引就指向陣列中一項,這一項裡面的儲存結構大概是,占用執行緒id,鎖的引用次數,等。當在例項成員中用lock關鍵字時,lock的就是本物件的同步塊索引指向的同步塊,如果在類成員中用lock時,指向的就是型別物件的同步塊索引指向的同步塊。

建議用lock時要特別小心,因為他鎖的永遠是本物件的同步塊,如果有執行緒巢狀的情況就會產生死鎖。

建議改用monitor,自已初始化乙個私有變數傳入做為鎖定物件。

C 學習 多執行緒程式設計 多執行緒基礎

c 內建了對多執行緒程式設計的支援功能,所以相對於其他語言在多執行緒方面的問題,c 這裡就已經最小化或者不復存在。在.net framework 4.0中,c 中新增了兩個與多執行緒應用程式相關的重要功能 tpl 任務執行並行庫 和plinq 並行linq 兩者都提供對並行程式設計的支援,都可以利用...

C 多執行緒應用基礎

定義命名空間 在.net中,多執行緒功能是在system.threading命名空間中定義的。using system.threading 啟動執行緒 system.threading命名空間中的thread類代表乙個執行緒物件,用這個類物件可以建立新的執行緒,刪除 暫停和恢復執行緒。下面的 使用t...

C 多執行緒程式設計基礎

使用執行緒有幾個原因。假設從應用程式中進行網路呼叫需要一定的時間。使用者不希望分割使用者界,並且讓使用者一直等待直到從伺服器返回乙個響應為止。使用者可以同時執行其他一些操作,或者甚至取消傳送給伺服器的請求。這些都可以使用執行緒來實現。對於所有需要等待的操作,例如,因為檔案 資料庫或網路訪問都需要一定...