C#多線程編程中的鎖系統

C#多線程編程中的鎖系統(二)


 

上章主要講排他鎖的直接使用方式。但實際當中所有都用鎖又太浪費了,或者排他鎖粒度太大了。 這一次咱們說說升級鎖和原子操做。php

目錄
1:volatile
2:  Interlocked
3:ReaderWriterLockSlim
4:總結html

一:volatile編程

簡單來講: volatile關鍵字是告訴c#編譯器和JIT編譯器,不對volatile標記的字段作任何的緩存。確保字段讀寫都是原子操做,最新值。c#

這不就是鎖嗎?   其這貨它根本不是鎖, 它的原子操做是基於CPU自己的,非阻塞的。 由於32位CPU執行賦值指令,數據傳輸最大寬度4個字節。緩存

因此只要在4個字節如下讀寫操做的,32位CPU都是原子操做。volatile 它就是利用這個特性來的。多線程

好殘酷的事實?否則,微軟大法這樣是爲了提升JIT性能效率,對有些數據進行緩存了(多線程下)。併發

 

複製代碼 代碼以下:

  //正確
       public volatile Int32 score1 = 1;
        //報錯
        public volatile Int64 score2 = 1;

 

看上面的例子,咱們定義8個字節長度score2就不行了。  由於8個字節,32位CPU就分紅2個指令執行了。天然就沒法保證原子操做了。函數

這麼細節的,忘了怎麼辦,那豈不是坑人啊。  因而微軟大法直接一棍子打死,限制4個字節如下的類型字段才能用volatile,具體什麼、看msdn吧。高併發

 

那今天我知道了。我編譯平臺改爲64位上,只在64位CPU用volatile  int64,行不行?  不行,編譯器報錯。說了一棍子打死了。。性能

(^._.^)ノ  好吧,其實能夠用IntPtr這個。

 volatile多數狀況下頗有用處的,畢竟鎖的性能開銷仍是很大的。咱們能夠把當成輕量級的鎖,根據具體場景合理使用,能提升很多程序性能。

線程中的Thread.VolatileRead 和Thread.VolatileWrite 就是volatile的複雜版。

二:Interlocked

MSDN 描述:爲多個線程共享的變量提供原子操做。主要函數以下:

Interlocked.Increment    原子操做,遞增指定變量的值並存儲結果。
Interlocked.Decrement       原子操做,遞減指定變量的值並存儲結果。
Interlocked.Add        原子操做,添加兩個整數並用二者的和替換第一個整數

Interlocked.CompareExchange(ref a, b, c);  原子操做,a參數和c參數比較,  相等b替換a,不相等不替換。

基本用法就很少說了。直接來段CLR via C# interlock anything的例子:

複製代碼 代碼以下:

public static int Maximum(ref int target, int value)
        {
            int currentVal = target, startVal, desiredVal;  //記錄先後值
            do
            {
                startVal = currentVal; //記錄循環迭代的初始值。
                desiredVal = Math.Max(startVal, value); //基於startVal和value計算指望值desiredVal

 

                //高併發下,線程被搶佔狀況下,target值會發生改變。

                //target startVal相等說明沒改變。desiredVal 直接替換。
                currentVal = Interlocked.CompareExchange(ref target, desiredVal, startVal);

            } while (startVal != currentVal); //不相等說明,target值已經被其餘線程改動。自旋繼續。
            return desiredVal;
        }

 

三:ReaderWriterLockSlim

假如咱們有份緩存數據A,若是每次都無論任何操做lock一下,那麼個人這份緩存A就永遠只能單線程讀寫了, 這在Web高併發下是不能忍受的。

那有沒有一種辦法我只在寫入時進入獨佔鎖呢,讀操做時不限制線程數量呢?答案就是咱們的ReaderWriterLockSlim主角,讀寫鎖。

ReaderWriterLockSlim 其中一種鎖EnterUpgradeableReadLock最關鍵  便可升級鎖。 

它呢容許你先進入讀鎖,發現緩存A不同了, 再進入寫鎖,寫入後退回讀鎖模式。

ps: 這裏注意下net 3.5以前有個ReaderWriterLock 性能較差。推薦使用升級版的 ReaderWriterLockSlim 。

複製代碼 代碼以下:

//實例一個讀寫鎖
 ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);

上面實例一個讀寫鎖,這裏注意的是構造函數的枚舉。

 

LockRecursionPolicy.NoRecursion 不支持,發現遞歸會拋異常。

LockRecursionPolicy.SupportsRecursion  即支持遞歸模式,線程鎖中繼續在使用鎖。

 

複製代碼 代碼以下:

cacheLock.EnterReadLock();
            //do
                cacheLock.EnterReadLock();
                //do
                cacheLock.ExitReadLock();
            cacheLock.ExitReadLock();

 

這種模式極易容易死鎖,好比讀鎖裏面使用寫鎖。

複製代碼 代碼以下:

cacheLock.EnterReadLock();
            //do
              cacheLock.EnterWriteLock();
              //do
              cacheLock.ExitWriteLock();
            cacheLock.ExitReadLock();

 

下面是直接拿msdn的緩存例子了,加了簡單註釋。

複製代碼 代碼以下:

public class SynchronizedCache
    {
        private ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim();
        private Dictionary<int, string> innerCache = new Dictionary<int, string>();

 

        public string Read(int key)
        {
            //進入讀鎖,容許其餘全部的讀線程,寫入線程被阻塞。
            cacheLock.EnterReadLock();
            try
            {
                return innerCache[key];
            }
            finally
            {
                cacheLock.ExitReadLock();
            }
        }

        public void Add(int key, string value)
        {
            //進入寫鎖,其餘全部訪問操做的線程都被阻塞。即寫獨佔鎖。
            cacheLock.EnterWriteLock();
            try
            {
                innerCache.Add(key, value);
            }
            finally
            {
                cacheLock.ExitWriteLock();
            }
        }

        public bool AddWithTimeout(int key, string value, int timeout)
        {
            //超時設置,若是在超時時間內,其餘寫鎖還不釋放,就放棄操做。
            if (cacheLock.TryEnterWriteLock(timeout))
            {
                try
                {
                    innerCache.Add(key, value);
                }
                finally
                {
                    cacheLock.ExitWriteLock();
                }
                return true;
            }
            else
            {
                return false;
            }
        }

        public AddOrUpdateStatus AddOrUpdate(int key, string value)
        {
            //進入升級鎖。 同時只能有一個可升級鎖線程。寫鎖,升級鎖都被阻塞,但容許其餘讀取數據的線程。
            cacheLock.EnterUpgradeableReadLock();
            try
            {
                string result = null;
                if (innerCache.TryGetValue(key, out result))
                {
                    if (result == value)
                    {
                        return AddOrUpdateStatus.Unchanged;
                    }
                    else
                    {
                        //升級成寫鎖,其餘全部線程都被阻塞。
                        cacheLock.EnterWriteLock();
                        try
                        {
                            innerCache[key] = value;
                        }
                        finally
                        {
                            //退出寫鎖,容許其餘讀線程。
                            cacheLock.ExitWriteLock();
                        }
                        return AddOrUpdateStatus.Updated;
                    }
                }
                else
                {
                    cacheLock.EnterWriteLock();
                    try
                    {
                        innerCache.Add(key, value);
                    }
                    finally
                    {
                        cacheLock.ExitWriteLock();
                    }
                    return AddOrUpdateStatus.Added;
                }
            }
            finally
            {
                //退出升級鎖。
                cacheLock.ExitUpgradeableReadLock();
            }
        }

        public enum AddOrUpdateStatus
        {
            Added,
            Updated,
            Unchanged
        };
    }

 

四:總結

多線程實際開發當中,每每測試沒問題,一到生產環境,併發高了就容易出問題, 必定注意。

本文參考CLR via C#。

 

 

出處:https://www.xp.cn/b.php/60358.html

相關文章
相關標籤/搜索