程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> C#多線程學習(三) 生產者和消費者

C#多線程學習(三) 生產者和消費者

編輯:C#入門知識

前面說過,每個線程都有自己的資源,但是代碼區是共享的,即每個線程都可以執行相同的函數。這可能帶來的問題就是幾個線程同時執行一個函數,導致數據的混亂,產生不可預料的結果,因此我們必須避免這種情況的發生。
C#提供了一個關鍵字lock,它可以把一段代碼定義為互斥段(critical section),互斥段在一個時刻內只允許一個線程進入執行,而其他線程必須等待。在C#中,關鍵字lock定義如下:
lock(expression) statement_block
expression代表你希望跟蹤的對象,通常是對象引用。
如果你想保護一個類的實例,一般地,你可以使用this;
如果你想保護一個靜態變量(如互斥代碼段在一個靜態方法內部),一般使用類名就可以了。
而statement_block就是互斥段的代碼,這段代碼在一個時刻內只可能被一個線程執行。
下面是一個使用lock關鍵字的典型例子,在注釋裡說明了lock關鍵字的用法和用途。
示例如下:
using System;
using System.Threading;

namespace ThreadSimple
{
    internal class Account
    {
        int balance;
        Random r = new Random();
       
        internal Account(int initial)
        {
            balance = initial;
        }
        internal int Withdraw(int amount)
        {
            if (balance < 0)
            {
                //如果balance小於0則拋出異常
                throw new Exception("Negative Balance");
            }
            //下面的代碼保證在當前線程修改balance的值完成之前
            //不會有其他線程也執行這段代碼來修改balance的值
            //因此,balance的值是不可能小於0 的
            lock (this)
            {
                Console.WriteLine("Current Thread:"+Thread.CurrentThread.Name);
                //如果沒有lock關鍵字的保護,那麼可能在執行完if的條件判斷之後
                //另外一個線程卻執行了balance=balance-amount修改了balance的值
                //而這個修改對這個線程是不可見的,所以可能導致這時if的條件已經不成立了
                //但是,這個線程卻繼續執行balance=balance-amount,所以導致balance可能小於0
                if (balance >= amount)
                {
                    Thread.Sleep(5);
                    balance = balance - amount;
                    return amount;
                }
                else
                {
                    return 0; // transaction rejected
                  }
            }
        }
        internal void DoTransactions()
        {
            for (int i = 0; i < 100; i++)
            Withdraw(r.Next(-50, 100));
        }
    }
    internal class Test
    {
        static internal Thread[] threads = new Thread[10];
        public static void Main()
        {
            Account acc = new Account (0);
            for (int i = 0; i < 10; i++)
            {
                Thread t = new Thread(new ThreadStart(acc.DoTransactions));
                threads[i] = t;
            }
            for (int i = 0; i < 10; i++)
                threads[i].Name=i.ToString();
            for (int i = 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類來實現線程的同步和通訊的例子,也是一個典型的生產者與消費者問題。<

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved