C 多執行緒學習 三 生產者和消費者

2021-06-05 14:52:51 字數 4381 閱讀 5083

前面說過,每個執行緒都有自己的資源,但是**區是共享的,即每個執行緒都可以執行相同的函式。這可能帶來的問題就是幾個執行緒同時執行乙個函式,導致資料的混亂,產生不可預料的結果,因此我們必須避免這種情況的發生。

c#提供了乙個關鍵字lock,它可以把一段**定義為互斥段(critical section),互斥段在乙個時刻內只允許乙個執行緒進入執行,而其他執行緒必須等待。在c#中,關鍵字lock定義如下:

lock(expression) statement_block

expression代表你希望跟蹤的物件,通常是物件引用。

如果你想保護乙個類的例項,一般地,你可以使用this;

如果你想保護乙個靜態變數(如互斥**段在乙個靜態方法內部),一般使用類名就可以了。

而statement_block就是互斥段的**,這段**在乙個時刻內只可能被乙個執行緒執行。

下面是乙個使用lock關鍵字的典型例子,在注釋裡說明了lock關鍵字的用法和用途。

示例如下:

using

system;

using

system.threading;

namespace

thread******

internal

intwithdraw(

int amount) 

// 下面的**保證在當前執行緒修改balance的值完成之前

// 不會有其他執行緒也執行這段**來修改balance的值

//因此,balance的值是不可能小於0 的

lock

(this

) else }

}internal

void

dotransactions() 

} internal

class

test 

for(

inti =0

; i 

<

10; i

++ ) 

threads[i].name

= i.tostring();

for(

inti =0

; i 

<

10; i

++ ) 

threads[i].start();

console.readline();}}

}

monitor 類鎖定乙個物件

當多執行緒公用乙個物件時,也會出現和公用**類似的問題,這種問題就不應該使用lock關鍵字了,這裡需要用到system.threading中的乙個類monitor,我們可以稱之為監視器,monitor提供了使執行緒共享資源的方案。

monitor類可以鎖定乙個物件,乙個執行緒只有得到這把鎖才可以對該物件進行操作。物件鎖機制保證了在可能引起混亂的情況下乙個時刻只有乙個執行緒可以訪問這個物件。

monitor必須和乙個具體的物件相關聯,但是由於它是乙個靜態的類,所以不能使用它來定義物件,而且它的所有方法都是靜態的,不能使用物件來引用。下面**說明了使用monitor鎖定乙個物件的情形:

......

queue oqueue=new queue();

......

monitor.enter(oqueue);

......//現在oqueue物件只能被當前執行緒操縱了

monitor.exit(oqueue);//釋放鎖 

如上所示,當乙個執行緒呼叫monitor.enter()方法鎖定乙個物件時,這個物件就歸它所有了,其它執行緒想要訪問這個物件,只有等待它使用monitor.exit()方法釋放鎖。為了保證執行緒最終都能釋放鎖,你可以把monitor.exit()方法寫在try-catch-finally結構中的finally**塊裡。

對於任何乙個被monitor鎖定的物件,記憶體中都儲存著與它相關的一些資訊:

其一是現在持有鎖的執行緒的引用;

其二是乙個預備隊列,佇列中儲存了已經準備好獲取鎖的執行緒;

其三是乙個等待佇列,佇列中儲存著當前正在等待這個物件狀態改變的佇列的引用。

當擁有物件鎖的執行緒準備釋放鎖時,它使用monitor.pulse()方法通知等待佇列中的第乙個執行緒,於是該執行緒被轉移到預備隊列中,當物件鎖被釋放時,在預備隊列中的執行緒可以立即獲得物件鎖。

下面是乙個展示如何使用lock關鍵字和monitor類來實現執行緒的同步和通訊的例子,也是乙個典型的生產者與消費者問題。

這個例程中,生產者執行緒和消費者執行緒是交替進行的,生產者寫入乙個數,消費者立即讀取並且顯示(注釋中介紹了該程式的精要所在)。

用到的系統命名空間如下:

using system;

using system.threading;

首先,定義乙個被操作的物件的類cell,在這個類裡,有兩個方法:readfromcell()和writetocell。消費者執行緒將呼叫readfromcell()讀取cellcontents的內容並且顯示出來,生產者程序將呼叫writetocell()方法向cellcontents寫入資料。

示例如下:

public

class

cell

catch

(synchronizationlockexception e)

catch

(threadinterruptedexception e)

}console.writeline(

"consume: 

" ,cellcontents);

readerflag 

=false

;//重置readerflag標誌,表示消費行為已經完成

monitor.pulse(

this

); //通知writetocell()方法(該方法在另外乙個執行緒中執行,等待中) }

return

cellcontents;

}public

void

writetocell(

int n)

catch

(synchronizationlockexception e)

catch

(threadinterruptedexception e)

}cellcontents 

= n;

console.writeline(

"produce: 

" ,cellcontents);

readerflag 

=true

; monitor.pulse(

this

); //通知另外乙個執行緒中正在等待的readfromcell()方法 }

}}

下面定義生產者類 cellprod 和消費者類 cellcons ,它們都只有乙個方法threadrun(),以便在main()函式中提供給執行緒的threadstart**物件,作為執行緒的入口。

public

class

cellprod

public

void

threadrun( )

}public

class

cellcons

public

void

threadrun( )} 

然後在下面這個類monitorsample的main()函式中,我們要做的就是建立兩個執行緒分別作為生產者和消費者,使用cellprod.threadrun()方法和cellcons.threadrun()方法對同乙個cell物件進行操作。

public

class

monitorsample

catch

(threadstateexception e)

catch

(threadinterruptedexception e)

//儘管main()函式沒有返回值,但下面這條語句可以向父程序返回執行結果

environment.exitcode 

= result;}}

在上面的例程中,同步是通過等待monitor.pulse()來完成的。首先生產者生產了乙個值,而同一時刻消費者處於等待狀態,直到收到生產者的「脈衝(pulse)」通知它生產已經完成,此後消費者進入消費狀態,而生產者開始等待消費者完成操作後將呼叫monitor.pulese()發出的「脈衝」。

它的執行結果很簡單:

produce: 1

consume: 1

produce: 2

consume: 2

produce: 3

consume: 3

...

...

produce: 20

consume: 20

事實上,這個簡單的例子已經幫助我們解決了多執行緒應用程式中可能出現的大問題,只要領悟了解決執行緒間衝突的基本方法,很容易把它應用到比較複雜的程式中去。

C 多執行緒 三 生產者和消費者

前面說過,每個執行緒都有自己的資源,但是 區是共享的,即每個執行緒都可以執行相同的函式。這可能帶來的問題就是幾個執行緒同時執行乙個函式,導致資料的混亂,產生不可預料的結果,因此我們必須避免這種情況的發生。c 提供了乙個關鍵字lock,它可以把一段 定義為互斥段 critical section 互斥...

C 多執行緒學習 三 生產者和消費者

前面說過,每個執行緒都有自己的資源,但是 區是共享的,即每個執行緒都可以執行相同的函式。這可能帶來的問題就是幾個執行緒同時執行乙個函式,導致資料的混亂,產生不可預料的結果,因此我們必須避免這種情況的發生。c 提供了乙個關鍵字lock,它可以把一段 定義為互斥段 critical section 互斥...

C 多執行緒學習 三 生產者和消費者

前面說過,每個執行緒都有自己的資源,但是 區是共享的,即每個執行緒都可以執行相同的函式。這可能帶來的問題就是幾個執行緒同時執行乙個函式,導致資料的混亂,產生不可預料的結果,因此我們必須避免這種情況的發生。c 提供了乙個關鍵字lock,它可以把一段 定義為互斥段 critical section 互斥...