程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 細說.NET中的多線程 (四 使用鎖進行同步),.net多線程

細說.NET中的多線程 (四 使用鎖進行同步),.net多線程

編輯:C#入門知識

細說.NET中的多線程 (四 使用鎖進行同步),.net多線程


通過鎖來實現同步

排它鎖主要用來保證,在一段時間內,只有一個線程可以訪問某一段代碼。兩種主要類型的排它鎖是lock和Mutex。Lock和Mutex相比構造起來更方便,運行的也更快。但是Mutex可以在同一個機器上的不同進程使用。

Monitor.Enter和Monitor.Exit

C#中的lock關鍵字,實際上是Monitor.Enter,Monitor.Exist的一個簡寫。在.NET 1.0,2.0,3.0 版本的c#中,lock會被編譯成如下代碼:

        Monitor.Enter(_locker);
        try
        {
            if (_val2 != 0) Console.WriteLine(_val1 / _val2);
            _val2 = 0;
        }
        finally { Monitor.Exit(_locker); }

如果你沒有調用Monitor.Enter而直接調用Monitor.Exit會引發異常。

LockTaken版本:

想象一下上面這段代碼,如果再Monitor.Enter之後,try之前,線程出現了異常(比如被終止),在這種情況下,finally中的Exit方法就永遠不會被執行,也就導致了這個鎖不會被釋放。為了避免這種情況,CLR 4.0的設計者重載了Monitor.Enter方法:

public static void Enter (object obj, ref bool lockTaken);

 

如果當前線程由於某些異常導致鎖沒有被獲取到,lockTake值會為false,因此在CLR 4.0中,lock會被解釋成如下代碼:

        bool lockTaken = false;
        try
        {

            Monitor.Enter(_locker, ref lockTaken);

            // Do your stuff...
        }

        finally { if (lockTaken) Monitor.Exit(_locker); }

TryEnter

Monitor也提供了了一個TryEnter方法,允許你設置一個超時時間,避免當前線程長時間獲取不到鎖而一直等待。

選擇正確的同步對象

你需要選擇一個對所有線程都可見的對象進行lock(obj)來確保程序能夠按照你的意圖執行。如果比不了解C#語言中的某些特性,lock可能不會按照你 期望來執行。

什麼時候使用lock

一個基本的規則,你需要對任意的寫操作,或者可修改的字段進行lock。即使是一個賦值操作,或者累加操作,你也不能假設他是線程安全的。

例如下面代碼不是線程安全的:

        class ThreadUnsafe
        {
            static int _x;
            static void Increment() { _x++; }
            static void Assign() { _x = 123; }
        }

 

你需要這樣寫:

        class ThreadSafe
        {
            static readonly object _locker = new object();
            static int _x;

            static void Increment() { lock (_locker) _x++; }
            static void Assign() { lock (_locker) _x = 123; }
        }

如果你看過一些BCL類庫裡面的實現,你可以能會發現,某些情況下會使用InterLocked類,而不是lock,我們會在後面介紹。

關於嵌套鎖或者reentrant

你在閱讀一些文檔的時候,有的文檔可能會說lock或者Monitor.Enter是reentrant(可重入的),那麼我們如何理解reentrant呢?

想象下以下代碼:

            lock (locker)
                lock (locker)
                    lock (locker)
                    {
                        // Do something...
                    }

 

或者是:

            Monitor.Enter(locker); Monitor.Enter(locker); Monitor.Enter(locker);
            // Do something...
            Monitor.Exit(locker); Monitor.Exit(locker); Monitor.Exit(locker);

這種情況下,只有在最後一個exit執行後,或者執行了相應次數的Exit後,locker才是可獲取的狀態。

Mutex

Mutex像c#中的lock一樣,但是在不同的進程中仍然可以使用。換句話說,Mutex是一個計算機級別的鎖。因此獲取這樣一個鎖要比Monitor慢很多。

示例代碼:

 

using System;
using System.Threading.Tasks;
using System.Threading;

namespace MultiThreadTest
{
    class OneAtATimePlease
    {
        static void Main()
        {
            // Naming a Mutex makes it available computer-wide. Use a name that's
            // unique to your company and application (e.g., include your URL).

            using (var mutex = new Mutex(false, "oreilly.com OneAtATimeDemo"))
            {
                // Wait a few seconds if contended, in case another instance
                // of the program is still in the process of shutting down.

                if (!mutex.WaitOne(TimeSpan.FromSeconds(3), false))
                {
                    Console.WriteLine("Another app instance is running. Bye!");
                    return;
                }
                RunProgram();
            }
        }

        static void RunProgram()
        {
            Console.WriteLine("Running. Press Enter to exit");
            Console.ReadLine();
        }
    }
}

Semaphore

Monitor和Mutex都是排他鎖,Semaphore我們常用的另外一種非排他的鎖。

我們用它來實現這樣一個例子:一個酒吧,最多能容納3人,如果客滿則需要等待,有客人離開,等待的人隨時可以進來。

示例代碼:

using System;
using System.Threading;

class TheClub      // No door lists!
{
    static Semaphore _sem = new Semaphore(3, 3);    // Capacity of 3

    static void Main()
    {
        for (int i = 1; i <= 5; i++) new Thread(Enter).Start(i);

        Console.ReadLine();
    }

    static void Enter(object id)
    {
        Console.WriteLine(id + " wants to enter");
        _sem.WaitOne();
        Console.WriteLine(id + " is in!");           // Only three threads
        Thread.Sleep(1000 * (int)id);               // can be here at
        Console.WriteLine(id + " is leaving");       // a time.
        _sem.Release();
    }
}

 

使用Semaphore需要調用者來控制訪問資源,調用WaitOne來獲取資源,通過Release來釋放資源。開發者有責任確保資源能夠正確釋放。

Semaphore在限制同步訪問的時候非常有用,它不會像Monitor或者Mutex那樣當一個線程訪問某些資源時,其它所有線程都需要等,而是設置一個緩沖區,允許最多多少個線程同時進行訪問。

Semaphore也可以像Mutex一樣,跨進程進行同步。

 

本節主要總結了使用鎖進行同步,下一節將總結使用信號量進行同步。      

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