C# lock 關鍵字的一些理解


 C# lock 關鍵字的一些理解

問題1:誰是鎖?

lock 這個關鍵字,並不是“鎖”,真正的“鎖”是那個被lock的Object類型的“對象”,請注意,這里為“對象”加了雙引號着重強調被lock的是對象類型。

問題2:這個鎖有什么用?

在C# lock關鍵字定義如下:

lock(expression) statement_block  //其中expression代表你希望跟蹤的對象,通常是對象引用。

根據lock的定義,它有兩種作用

作用1:鎖住括號中的對象

只讓當前線程擁有該對象的變量、方法、屬性等的使用權,lock后一個時刻只可能被一個線程操作。

如果你想保護一個類的實例,一般地,你可以使用this;如果你想保護一個靜態變量(如互斥代碼段在一個靜態方法內部),一般使用類名就可以了。

作用2:鎖住()后面的代碼塊(一般放在{}中)

就是定義中的statement_block,這里代表互斥段的代碼,這段代碼在一個時刻內只可能被一個線程執行。

舉個例子,多數商場廁所的蹲位都是小單間型的,也就是一次只能進去一個人,商如何確保每次只能進去一個人呢?不就是一個人進去之后順手把門鎖上么?這樣你在里面干啥事,外邊的人也只能等待你解放完了,才能進入。以此類推,某個 object 對象被lock之后,lock這個對象的那個線程就擁有了執行lock()后面{}的完整的獨立執行權,完整且獨立的執行,不可分割的執行,也就是說,{}是一塊臨界代碼段。

看下面代碼

private static object objlock = new object();
lock (objlock)
{
    //這里要做一些事情
}

根據問題1的答案進行推導,objlock才是那把鎖,lock 一下,當前線程就獲得了objlock對象緊跟在lock()后面的{}的獨立使用權,在此期間,其他誰都得等着擁有objlock鑰匙的線程執行完之后才能使用{}里面的代碼。

更專業一點的說法是:在.Net中,每個對象都有一個與之關聯的鎖,對象可以得到並釋放它以便在任意時間只有一個線程可以訪問對象實例變量和方法。同樣.Net中的每一個對象都可以提供一個允許自己進入等待狀態的機制。上面提到的獨立使用權,就是對象的互斥鎖。(關於這個說法,求證無果,只是從博客中找到的,msdn上暫時沒找到相關說法)。

問題3.為什么只能lock引用類型?

因為只有引用類型才有互斥鎖,如果強行lock值類型,c#會把值裝箱成引用類型,下一次再lock,還會裝箱,這兩次裝箱實際上是裝成了兩個箱子,就不是用一個內存區間了,所以鎖的概念就沒有意義。

問題4.避免lock public類型的對象是為什么?

還是以廁所為例子吧,私有就好比,這把鎖只有你能訪問到,而且最好這把鎖不會因為外力而有所改變,別人訪問不到,這樣才能保證你進去了,別人就進不去了,如果是公有的,就好比你蹲位小單間的鎖不是安裝在里面而是安裝在外邊的,別人想不想進就不是你所能控制的了,這樣也不安全。

問題5.下面代碼中_dic到底能不能被其他線程操作?

private static Dictionary<int, BoKwdCueItem> _dic=new Dictionary<int, BoKwdCueItem>();
static object _obj = new object();
lock (_obj)
{
///對_dic的寫操作
}

個人理解上 _dic 存在被其他線程修改的可能。

private static Dictionary<int, BoKwdCueItem> _dic=new Dictionary<int, BoKwdCueItem>();
static object _obj = new object();
lock (_obj)
{
    ///對_dic的寫操作
}

public void AddToDic(int data)
{
    _dic.Add(data);
}

 假設A線程執行到lock(_obj)的{}的一半的時候,B線程獲取的CPU的使用權,然后B線程調用了AddToDic方法,就會修改_dic的數據。

以上內容為網上搜集與個人理解,我也不保證一定對。 

問題6.為什么msdn建議的是使用lock鎖住private static readonly的對象?

private static readonly object obj = new object();

為什么要設置成只讀的呢?這時因為如果在lock代碼段中改變obj的值,其它線程就暢通無阻了,因為互斥鎖的對象變了,object.ReferenceEquals必然返回false。

至於private,上面已經提到了。

問題7.lock究竟是什么?

lock關鍵字其實就是封裝了Moniter類的一些操作,詳情介紹見msdn上關於lock的解釋,下面是原文摘錄。

lock 語句具有以下格式

lock (x)
{
    // Your code...
}

其中 x 是引用類型的表達式。 它完全等同於

object __lockObj = x;
bool __lockWasTaken = false;
try
{
    System.Threading.Monitor.Enter(__lockObj, ref __lockWasTaken);
    // Your code...
}
finally
{
    if (__lockWasTaken) System.Threading.Monitor.Exit(__lockObj);
}

關於Monitor類這里暫時不做過多的解釋。

倒是msdn上的一個示例非常值得借鑒,應該算是lock最簡單的應用方式了。以下示例定義了一個 Account 類,該類通過鎖定專用的 balanceLock 實例來同步對其專用 balance 字段的訪問。 使用相同的實例進行鎖定可確保嘗試同時調用 Debit 或 Credit 方法的兩個線程無法同時更新 balance 字段。

using System;
using System.Threading.Tasks;

public class Account
{
    private readonly object balanceLock = new object();
    private decimal balance;

    public Account(decimal initialBalance)
    {
        balance = initialBalance;
    }

    public decimal Debit(decimal amount)
    {
        lock (balanceLock)
        {
            if (balance >= amount)
            {
                Console.WriteLine($"Balance before debit :{balance, 5}");
                Console.WriteLine($"Amount to remove     :{amount, 5}");
                balance = balance - amount;
                Console.WriteLine($"Balance after debit  :{balance, 5}");
                return amount;
            }
            else
            {
                return 0;
            }
        }
    }

    public void Credit(decimal amount)
    {
        lock (balanceLock)
        {
            Console.WriteLine($"Balance before credit:{balance, 5}");
            Console.WriteLine($"Amount to add        :{amount, 5}");
            balance = balance + amount;
            Console.WriteLine($"Balance after credit :{balance, 5}");
        }
    }
}

class AccountTest
{
    static void Main()
    {
        var account = new Account(1000);
        var tasks = new Task[100];
        for (int i = 0; i < tasks.Length; i++)
        {
            tasks[i] = Task.Run(() => RandomlyUpdate(account));
        }
        Task.WaitAll(tasks);
    }

    static void RandomlyUpdate(Account account)
    {
        var rnd = new Random();
        for (int i = 0; i < 10; i++)
        {
            var amount = rnd.Next(1, 100);
            bool doCredit = rnd.NextDouble() < 0.5;
            if (doCredit)
            {
                account.Credit(amount);
            }
            else
            {
                account.Debit(amount);
            }
        }
    }
}

我想着重說明的一些部分:

a、balanceLock的用法與命名。balanceLock在邏輯上是人為綁定給balance用的“鎖”。

b、考慮這樣一個問題:如果上面代碼再加兩個方法直接在不lock balanceLock對象的情況下操作balance,那么這兩個方法在異步操作的時候,會出現什么情況??如果一個帶lock的方法和一個不帶lock的方法對balance異步操作又會出現什么情況??

答案在本文中找。


后記:好多重復,邏輯也有點亂,不打算改了,以上順序就是在思考lock關鍵字的時候就是不斷出現的問題的順序,留作思路。

參考文章:

https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/lock-statement

https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.monitor?redirectedfrom=MSDN&view=netframework-4.7.2

http://www.cnblogs.com/promise-7/articles/2354077.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM