.NET 同步與異步之鎖(ReaderWriterLockSlim)(八)


本隨筆續接:.NET 同步與異步之鎖(Lock、Monitor)(七)

 

由於鎖 ( lock 和 Monitor ) 是線程獨占式訪問的,所以其對性能的影響還是蠻大的,那有沒有一種方式可是實現:允許多個線程同時讀數據、只允許一個線程寫數據呢?答案是肯定的。

 

讀寫鎖 ReaderWriterLock 、就是 支持單個寫線程和多個讀線程的鎖。自.NET 3.5 開始 ReaderWriterLockSlim 、登上舞台,ReaderWriterLockSlim 可以看做是 ReaderWriterLock 的升級版。 由於 ReaderWriterLockSlim  默認不支持遞歸調用、所以在某種意義上來說更不容易造成死鎖。

 

一、先看一下demo(來源msdn代碼示例):

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

        public int Count
        { get { return innerCache.Count; } }

        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 void Delete(int key)
        {
            cacheLock.EnterWriteLock();
            try
            {
                innerCache.Remove(key);
            }
            finally
            {
                cacheLock.ExitWriteLock();
            }
        }

        public enum AddOrUpdateStatus
        {
            Added,
            Updated,
            Unchanged
        };

        ~SynchronizedCache()
        {
            if (cacheLock != null) cacheLock.Dispose();
        }
    }


        private void ReaderWriterLock()
        {
            var sc = new SynchronizedCache();
            var tasks = new List<Task>();
            int itemsWritten = 0;

            // Execute a writer.
            tasks.Add(Task.Run(() =>
            {
                String[] vegetables = { "broccoli", "cauliflower",
                                                          "carrot", "sorrel", "baby turnip",
                                                          "beet", "brussel sprout",
                                                          "cabbage", "plantain",
                                                          "spinach", "grape leaves",
                                                          "lime leaves", "corn",
                                                          "radish", "cucumber",
                                                          "raddichio", "lima beans" };
                for (int ctr = 1; ctr <= vegetables.Length; ctr++)
                    sc.Add(ctr, vegetables[ctr - 1]);

                itemsWritten = vegetables.Length;

                base.PrintInfo(string.Format("Task {0} wrote {1} items\n", Task.CurrentId, itemsWritten));
            }));
            // Execute two readers, one to read from first to last and the second from last to first.
            for (int ctr = 0; ctr <= 1; ctr++)
            {
                bool desc = Convert.ToBoolean(ctr);
                tasks.Add(Task.Run(() =>
                {
                    int start, last, step;
                    int items;
                    do
                    {
                        String output = String.Empty;
                        items = sc.Count;
                        if (!desc)
                        {
                            start = 1;
                            step = 1;
                            last = items;
                        }
                        else
                        {
                            start = items;
                            step = -1;
                            last = 1;
                        }

                        for (int index = start; desc ? index >= last : index <= last; index += step)
                            output += String.Format("[{0}] ", sc.Read(index));

                        base.PrintInfo(string.Format("Task {0} read {1} items: {2}\n", Task.CurrentId, items, output));

                    } while (items < itemsWritten | itemsWritten == 0);
                }));
            }
            // Execute a red/update task.
            tasks.Add(Task.Run(() =>
            {
                Thread.Sleep(100);
                for (int ctr = 1; ctr <= sc.Count; ctr++)
                {
                    String value = sc.Read(ctr);
                    if (value == "cucumber")
                        if (sc.AddOrUpdate(ctr, "green bean") != SynchronizedCache.AddOrUpdateStatus.Unchanged)
                            base.PrintInfo("Changed 'cucumber' to 'green bean'");
                }
            }));

            // Wait for all three tasks to complete.
            Task.WaitAll(tasks.ToArray());

            // Display the final contents of the cache.
            base.PrintInfo("");
            base.PrintInfo("Values in synchronized cache: ");
            for (int ctr = 1; ctr <= sc.Count; ctr++)
                base.PrintInfo(string.Format("   {0}: {1}", ctr, sc.Read(ctr)));
        }
Demo

 

二、通過Demo我們來看一下 ReaderWriterLockSlim  的用法:

1、EnterWriteLock   進入寫模式鎖定狀態

2、EnterReadLock    進入讀模式鎖定狀態

3、EnterUpgradeableReadLock  進入可升級的讀模式鎖定狀態

並且三種鎖定模式都有超時機制、對應 Try... 方法,退出相應的模式則使用 Exit... 方法,而且所有的方法都必須是成對出現的。

 

三、備注及注意事項

1、對於同一把鎖、多個線程可同時進入 讀模式。

2、對於同一把鎖、同時只允許一個線程進入 寫模式。

3、對於同一把鎖、同時只允許一個線程進入 可升級的讀模式。

 

4、通過默認構造函數創建的讀寫鎖是不支持遞歸的,若想支持遞歸 可通過構造 ReaderWriterLockSlim(LockRecursionPolicy) 創建實例。

5、對於同一把鎖、同一線程不可兩次進入同一鎖狀態(開啟遞歸后可以)

6、對於同一把鎖、即便開啟了遞歸、也不可以在進入讀模式后再次進入寫模式或者可升級的讀模式(在這之前必須退出讀模式)。

7、再次強調、不建議啟用遞歸。

8、讀寫鎖具有線程關聯性,即 兩個線程間擁有的鎖的狀態 相互獨立不受影響、並且不能相互修改其鎖的狀態。

 

9、升級狀態:在進入可升級的讀模式 EnterUpgradeableReadLock  后,可在恰當時間點 通過 EnterWriteLock   進入寫模式。

10、降級狀態:可升級的讀模式可以降級為讀模式:即 在進入可升級的讀模式 EnterUpgradeableReadLock  后, 通過首先調用讀取模式 EnterReadLock 方法,然后再調用 ExitUpgradeableReadLock 方法。 

 

 

隨筆暫告一段落、下一篇隨筆介紹:輕量級的鎖(Interlocked、SpinLock)(預計1篇隨筆)

附,Demo : http://files.cnblogs.com/files/08shiyan/ParallelDemo.zip

參見更多:隨筆導讀:同步與異步


(未完待續...)


免責聲明!

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



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