C# 多線程鎖之ReaderWriterLockSlim


1、簡介

.NET 3.5 開始 ReaderWriterLockSlim登上舞台,ReaderWriterLockSlim 可以看做是 ReaderWriterLock 的升級版。 由於 ReaderWriterLockSlim 默認不支持遞歸調用、所以在某種意義上來說更不容易造成死鎖。
ReaderWriterLockSlim 類支持三種鎖定模式:Read,Write,UpgradeableRead。這三種模式對應的方法分別是 EnterReadLock,EnterWriteLock,EnterUpgradeableReadLock 。再就是與此對應的 TryEnterReadLock,TryEnterWriteLock,TryEnterUpgradeableReadLock,ExitReadLock,ExitWriteLock,ExitUpgradeableReadLock。Read 和 Writer 鎖定模式比較簡單易懂:Read 模式是典型的共享鎖定模式,任意數量的線程都可以在該模式下同時獲得鎖;Writer 模式則是互斥模式,在該模式下只允許一個線程進入該鎖。UpgradeableRead 鎖定模式可能對於大多數人來說比較新鮮,但是在數據庫領域卻眾所周知。

1、對於同一把鎖、多個線程可同時進入讀模式。
2、對於同一把鎖、同時只允許一個線程進入寫模式。
3、對於同一把鎖、同時只允許一個線程進入可升級的讀模式。
4、通過默認構造函數創建的讀寫鎖是不支持遞歸的,若想支持遞歸 可通過構造 ReaderWriterLockSlim(LockRecursionPolicy) 創建實例。
5、對於同一把鎖、同一線程不可兩次進入同一鎖狀態(開啟遞歸后可以)
6、對於同一把鎖、即便開啟了遞歸、也不可以在進入讀模式后再次進入寫模式或者可升級的讀模式(在這之前必須退出讀模式)。
7、再次強調、不建議啟用遞歸。
8、讀寫鎖具有線程關聯性,即兩個線程間擁有的鎖的狀態相互獨立不受影響、並且不能相互修改其鎖的狀態。
9、升級狀態:在進入可升級的讀模式 EnterUpgradeableReadLock后,可在恰當時間點通過EnterWriteLock進入寫模式。
10、降級狀態:可升級的讀模式可以降級為讀模式:即在進入可升級的讀模式EnterUpgradeableReadLock后, 通過首先調用讀取模式EnterReadLock方法,然后再調用 ExitUpgradeableReadLock 方法。

這段簡介來自https://www.cnblogs.com/majiang/p/8133979.html,來自一個前輩的文章,總結的很好,而且有源碼解析,有興趣的可以觀看,通過這段話結合MSDN關於ReaderWriterLockSlim的介紹,能大致得知道ReaderWriterLockSlim得用處,在多線程並發操作共享資源時,很有用處.

 

2、通過ReaderWriterLockSlim封裝一個同步緩存實例

下面時MS提供的封裝,我做了略微的修改,添加了一些注釋,使API更能看懂,代碼如下:

 public class SynchronizedCache
    {
        private ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim();

        /// <summary>
        /// 同步緩存塊維護的數據資源
        /// </summary>
        private Dictionary<int, string> innerCache = new Dictionary<int, string>();

        /// <summary>
        /// 同步緩存塊維護的數據資源長度
        /// </summary>
        public int Count
        {
            get { return innerCache.Count; }
        }

        /// <summary>
        /// 線程安全的添加操作
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        public void Add(int key,string value)
        {
            //嘗試進入寫入模式鎖定狀態
            cacheLock.EnterWriteLock();
            try
            {
                innerCache.Add(key, value);
            }
            finally
            {
                //退出寫入模式鎖定狀態
                cacheLock.ExitWriteLock();
            }
        }

        /// <summary>
        /// 帶鎖超時的添加的操作
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="timeout"></param>
        /// <returns></returns>
        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;
            }
        }

        /// <summary>
        /// 線程安全的讀取操作
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public string Read(int key)
        {
            cacheLock.EnterReadLock();
            try
            {
                return innerCache[key];
            }
            finally
            {
                cacheLock.ExitReadLock();
            }
        }

        /// <summary>
        /// 線程安全的添加修改操作
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        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();
            }
        }

        /// <summary>
        /// 線程安全的刪除操作
        /// </summary>
        /// <param name="key"></param>
        public void Delete(int key)
        {
            cacheLock.EnterWriteLock();
            try
            {
                innerCache.Remove(key);
            }
            finally
            {
                cacheLock.ExitWriteLock();
            }
        }

        /// <summary>
        /// 添加或修改時產生的狀態
        /// </summary>
        public enum AddOrUpdateStatus
        {
            Added,
            Updated,
            Unchanged
        };

        /// <summary>
        /// 析構 釋放資源
        /// </summary>
        ~SynchronizedCache()
        {
            if (cacheLock != null) cacheLock.Dispose();
        }
    }

下面時使用案列代碼如下:

  var lockCache = new SynchronizedCache();
            var tasks = new List<Task>();//模擬線程集合

            //注入寫入內容線程
            tasks.Add(Task.Run(() => {
                var list = new List<string> {"","","","","","","","",""};
                var listCount = list.Count;
                for (var i = 0; i < listCount; i++)
                {
                    lockCache.Add(i, list[i]);
                }
                Console.WriteLine($"Task {Task.CurrentId} wrote {listCount} items\n");
            }));

            //注入兩個讀線程,一個正向遍歷同步緩存塊維護的數據資源一個逆向遍歷同步緩存塊維護的數據資源
            //由於讀線程可能在寫線程之前執行,所以輸入內容時可能為空
            for (var i = 0; i <= 1; i++)
            {
                var flag = Convert.ToBoolean(i);
                tasks.Add(Task.Run(() =>
                {
                    int startIndex, lastIndex, step;//開始、結束索引、遞增指數
                    string outPut=string.Empty;//輸出
                    int items;//線程執行順序可能不同,所以個參數用於判斷在執行讀取操作時,上面的寫入線程是否執行完畢
                    do
                    {
                        items = lockCache.Count;
                        //正向遍歷
                        if (!flag)
                        {
                            startIndex = 0;
                            lastIndex = items;
                            step = 1;
                        }
                        //反向遍歷
                        else
                        {
                            startIndex = items - 1;
                            lastIndex = 0;
                            step = -1;
                        }
                        for (var j = startIndex; flag ? j >= lastIndex : j < lastIndex; j += step)
                        {
                            outPut += $"{lockCache.Read(j)} ";
                        }
                        Console.WriteLine($"Task {Task.CurrentId} read {items} items: {outPut}\n"); 
                    } while (lockCache.Count == 0 | items< lockCache.Count);
                }));
            }

            //注入一個線程去修改數據
            tasks.Add(Task.Run(() => {
                Thread.Sleep(100);//強制當前線程休息,防止寫入數據線程還沒有執行完畢,就去更新了數據
                for (int ctr =0; ctr < lockCache.Count; ctr++)
                {
                    string value = lockCache.Read(ctr);
                    if (value == "")
                        if (lockCache.AddOrUpdate(ctr, "Home") != SynchronizedCache.AddOrUpdateStatus.Unchanged)
                            Console.WriteLine("Changed '家' to 'Home'");
                }
            }));

            Task.WhenAll(tasks).ContinueWith(task =>
            {
                Console.WriteLine();
                Console.WriteLine("Values in synchronized cache: ");
                for (int ctr = 0; ctr < lockCache.Count; ctr++)
                    Console.WriteLine("   {0}: {1}", ctr, lockCache.Read(ctr));
            });
            
            Console.ReadKey();

調用完畢,有點ConncurrentDictionary的味道,還沒看它的代碼,接下去的隨筆會分析,對比下兩種方式的差距.

 


免責聲明!

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



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