欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C#多線程系列之讀寫鎖

 更新時間:2022年02月13日 17:13:58   作者:癡者工良  
本文詳細(xì)講解了C#多線程的讀寫鎖,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下

本篇的內(nèi)容主要是介紹 ReaderWriterLockSlim 類,來實現(xiàn)多線程下的讀寫分離。

ReaderWriterLockSlim

ReaderWriterLock 類:定義支持單個寫線程和多個讀線程的鎖。

ReaderWriterLockSlim 類:表示用于管理資源訪問的鎖定狀態(tài),可實現(xiàn)多線程讀取或進(jìn)行獨占式寫入訪問。

兩者的 API 十分接近,而且 ReaderWriterLockSlim 相對 ReaderWriterLock 來說 更加安全。因此本文主要講解 ReaderWriterLockSlim 。

兩者都是實現(xiàn)多個線程可同時讀取、只允許一個線程寫入的類。

ReaderWriterLockSlim

老規(guī)矩,先大概了解一下 ReaderWriterLockSlim 常用的方法。

常用方法

方法說明
EnterReadLock()嘗試進(jìn)入讀取模式鎖定狀態(tài)。
EnterUpgradeableReadLock()嘗試進(jìn)入可升級模式鎖定狀態(tài)。
EnterWriteLock()嘗試進(jìn)入寫入模式鎖定狀態(tài)。
ExitReadLock()減少讀取模式的遞歸計數(shù),并在生成的計數(shù)為 0(零)時退出讀取模式。
ExitUpgradeableReadLock()減少可升級模式的遞歸計數(shù),并在生成的計數(shù)為 0(零)時退出可升級模式。
ExitWriteLock()減少寫入模式的遞歸計數(shù),并在生成的計數(shù)為 0(零)時退出寫入模式。
TryEnterReadLock(Int32)嘗試進(jìn)入讀取模式鎖定狀態(tài),可以選擇整數(shù)超時時間。
TryEnterReadLock(TimeSpan)嘗試進(jìn)入讀取模式鎖定狀態(tài),可以選擇超時時間。
TryEnterUpgradeableReadLock(Int32)嘗試進(jìn)入可升級模式鎖定狀態(tài),可以選擇超時時間。
TryEnterUpgradeableReadLock(TimeSpan)嘗試進(jìn)入可升級模式鎖定狀態(tài),可以選擇超時時間。
TryEnterWriteLock(Int32)嘗試進(jìn)入寫入模式鎖定狀態(tài),可以選擇超時時間。
TryEnterWriteLock(TimeSpan)嘗試進(jìn)入寫入模式鎖定狀態(tài),可以選擇超時時間。

ReaderWriterLockSlim 的讀、寫入鎖模板如下:

        private static ReaderWriterLockSlim toolLock = new ReaderWriterLockSlim();

		// 讀
        private T Read()
        {

            try
            {
                toolLock.EnterReadLock();           // 獲取讀取鎖
                return obj;
            }
            catch { }
            finally
            {
                toolLock.ExitReadLock();            // 釋放讀取鎖
            }
            return default;
        }

        // 寫
        public void Write(int key, int value)
        {
            try
            {
                toolLock.EnterUpgradeableReadLock();

                try
                {
                    toolLock.EnterWriteLock();
                    /*
                     * 
                    */
                }
                catch
                {

                }
                finally
                {
                    toolLock.ExitWriteLock();
                }
            }
            catch { }
            finally
            {
                toolLock.ExitUpgradeableReadLock();
            }
        }

訂單系統(tǒng)示例

這里來模擬一個簡單粗糙的訂單系統(tǒng)。

開始編寫代碼前,先來了解一些方法的具體使用。

EnterReadLock() / TryEnterReadLock 和 ExitReadLock() 成對出現(xiàn)。

EnterWriteLock() / TryEnterWriteLock() 和 ExitWriteLock() 成對出現(xiàn)。

EnterUpgradeableReadLock() 進(jìn)入可升級的讀模式鎖定狀態(tài)。

EnterReadLock() 使用 EnterUpgradeableReadLock() 進(jìn)入升級狀態(tài),在恰當(dāng)時間點 通過 EnterWriteLock() 進(jìn)入寫模式。(也可以倒過來)

定義三個變量:

ReaderWriterLockSlim 多線程讀寫鎖;

MaxId 當(dāng)前訂單 Id 的最大值;

orders 訂單表;

        private static ReaderWriterLockSlim tool = new ReaderWriterLockSlim();   // 讀寫鎖

        private static int MaxId = 1;
        public static List<DoWorkModel> orders = new List<DoWorkModel>();       // 訂單表
        // 訂單模型
        public class DoWorkModel
        {
            public int Id { get; set; }     // 訂單號
            public string UserName { get; set; }    // 客戶名稱
            public DateTime DateTime { get; set; }  // 創(chuàng)建時間
        }

然后實現(xiàn)查詢和創(chuàng)建訂單的兩個方法。

分頁查詢訂單:

在讀取前使用 EnterReadLock() 獲取鎖;

讀取完畢后,使用 ExitReadLock() 釋放鎖。

這樣能夠在多線程環(huán)境下保證每次讀取都是最新的值。

        // 分頁查詢訂單
        private static DoWorkModel[] DoSelect(int pageNo, int pageSize)
        {

            try
            {
                DoWorkModel[] doWorks;
                tool.EnterReadLock();           // 獲取讀取鎖
                doWorks = orders.Skip((pageNo - 1) * pageSize).Take(pageSize).ToArray();
                return doWorks;
            }
            catch { }
            finally
            {
                tool.ExitReadLock();            // 釋放讀取鎖
            }
            return default;
        }

創(chuàng)建訂單:

創(chuàng)建訂單的信息十分簡單,知道用戶名和創(chuàng)建時間就行。

訂單系統(tǒng)要保證的時每個 Id 都是唯一的(實際情況應(yīng)該用Guid),這里為了演示讀寫鎖,設(shè)置為 數(shù)字。

在多線程環(huán)境下,我們不使用 Interlocked.Increment() ,而是直接使用 += 1,因為有讀寫鎖的存在,所以操作也是原則性的。

        // 創(chuàng)建訂單
        private static DoWorkModel DoCreate(string userName, DateTime time)
        {
            try
            {
                tool.EnterUpgradeableReadLock();        // 升級
                try
                {
                    tool.EnterWriteLock();              // 獲取寫入鎖

                    // 寫入訂單
                    MaxId += 1;                         // Interlocked.Increment(ref MaxId);

                    DoWorkModel model = new DoWorkModel
                    {
                        Id = MaxId,
                        UserName = userName,
                        DateTime = time
                    };
                    orders.Add(model);
                    return model;
                }
                catch { }
                finally
                {
                    tool.ExitWriteLock();               // 釋放寫入鎖
                }
            }
            catch { }
            finally
            {
                tool.ExitUpgradeableReadLock();         // 降級
            }
            return default;
        }

Main 方法中:

開 5 個線程,不斷地讀,開 2 個線程不斷地創(chuàng)建訂單。線程創(chuàng)建訂單時是沒有設(shè)置 Thread.Sleep() 的,因此運行速度十分快。

Main 方法里面的代碼沒有什么意義。

        static void Main(string[] args)
        {
            // 5個線程讀
            for (int i = 0; i < 5; i++)
            {
                new Thread(() =>
                {
                    while (true)
                    {
                        var result = DoSelect(1, MaxId);
                        if (result is null)
                        {
                            Console.WriteLine("獲取失敗");
                            continue;
                        }
                        foreach (var item in result)
                        {
                            Console.Write($"{item.Id}|");
                        }
                        Console.WriteLine("\n");
                        Thread.Sleep(1000);
                    }
                }).Start();
            }

            for (int i = 0; i < 2; i++)
            {
                new Thread(() =>
                {
                    while(true)
                    {
                        var result = DoCreate((new Random().Next(0, 100)).ToString(), DateTime.Now);      // 模擬生成訂單
                        if (result is null)
                            Console.WriteLine("創(chuàng)建失敗");
                        else Console.WriteLine("創(chuàng)建成功");
                    }

                }).Start();
            }
        }

在 ASP.NET Core 中,則可以利用讀寫鎖,解決多用戶同時發(fā)送 HTTP 請求帶來的數(shù)據(jù)庫讀寫問題。

這里就不做示例了。

如果另一個線程發(fā)生問題,導(dǎo)致遲遲不能交出寫入鎖,那么可能會導(dǎo)致其它線程無限等待。

那么可以使用 TryEnterWriteLock() 并且設(shè)置等待時間,避免阻塞時間過長。

bool isGet = tool.TryEnterWriteLock(500);

并發(fā)字典寫示例

因為理論的東西,筆者這里不會說太多,主要就是先掌握一些 API(方法、屬性) 的使用,然后簡單寫出示例,后面再慢慢深入了解底層原理。

這里來寫一個多線程共享使用字典(Dictionary)的使用示例。

增加兩個靜態(tài)變量:

        private static ReaderWriterLockSlim toolLock = new ReaderWriterLockSlim();
        private static Dictionary<int, int> dict = new Dictionary<int, int>();

實現(xiàn)一個寫操作:

        public static void Write(int key, int value)
        {
            try
            {
                // 升級狀態(tài)
                toolLock.EnterUpgradeableReadLock();
                // 讀,檢查是否存在
                if (dict.ContainsKey(key))
                    return;

                try
                {
                    // 進(jìn)入寫狀態(tài)
                    toolLock.EnterWriteLock();
                    dict.Add(key,value);
                }
                finally
                {
                    toolLock.ExitWriteLock();
                }
            }
            finally
            {
                toolLock.ExitUpgradeableReadLock();
            }
        }

上面沒有 catch { } 是為了更好觀察代碼,因為使用了讀寫鎖,理論上不應(yīng)該出現(xiàn)問題的。

模擬五個線程同時寫入字典,由于不是原子操作,所以 sum 的值有些時候會出現(xiàn)重復(fù)值。

原子操作請參考:http://www.dbjr.com.cn/article/237310.htm

        private static int sum = 0;
        public static void AddOne()
        {
            for (int i = 0; i < 100_0000; i++)
            {
                sum += 1;
                Write(sum,sum);
            }
        }
        static void Main(string[] args)
        {
            for (int i = 0; i < 5; i++)
                new Thread(() => { AddOne(); }).Start();
            Console.ReadKey();
        }

ReaderWriterLock

大多數(shù)情況下都是推薦 ReaderWriterLockSlim 的,而且兩者的使用方法十分接近。

例如 AcquireReaderLock 是獲取讀鎖,AcquireWriterLock 獲取寫鎖。使用對應(yīng)的方法即可替換 ReaderWriterLockSlim 中的示例。

這里就不對 ReaderWriterLock 進(jìn)行贅述了。

ReaderWriterLock 的常用方法如下:

方法說明
AcquireReaderLock(Int32)使用一個 Int32 超時值獲取讀線程鎖。
AcquireReaderLock(TimeSpan)使用一個 TimeSpan 超時值獲取讀線程鎖。
AcquireWriterLock(Int32)使用一個 Int32 超時值獲取寫線程鎖。
AcquireWriterLock(TimeSpan)使用一個 TimeSpan 超時值獲取寫線程鎖。
AnyWritersSince(Int32)指示獲取序列號之后是否已將寫線程鎖授予某個線程。
DowngradeFromWriterLock(LockCookie)將線程的鎖狀態(tài)還原為調(diào)用 UpgradeToWriterLock(Int32) 前的狀態(tài)。
ReleaseLock()釋放鎖,不管線程獲取鎖的次數(shù)如何。
ReleaseReaderLock()減少鎖計數(shù)。
ReleaseWriterLock()減少寫線程鎖上的鎖計數(shù)。
RestoreLock(LockCookie)將線程的鎖狀態(tài)還原為調(diào)用 ReleaseLock() 前的狀態(tài)。
UpgradeToWriterLock(Int32)使用一個 Int32 超時值將讀線程鎖升級為寫線程鎖。
UpgradeToWriterLock(TimeSpan)使用一個 TimeSpan 超時值將讀線程鎖升級為寫線程鎖。

官方示例可以看:

https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.readerwriterlock?view=netcore-3.1#examples

到此這篇關(guān)于C#多線程系列之讀寫鎖的文章就介紹到這了。希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • C#中事務(wù)處理和非事務(wù)處理方法實例分析

    C#中事務(wù)處理和非事務(wù)處理方法實例分析

    這篇文章主要介紹了C#中事務(wù)處理和非事務(wù)處理方法,較為詳細(xì)的分析了C#中事務(wù)處理與非事務(wù)處理的使用技巧,對于使用C#進(jìn)行數(shù)據(jù)庫程序開發(fā)有一定參考借鑒價值,需要的朋友可以參考下
    2015-07-07
  • c#使用EPPlus將圖片流嵌入到Excel實現(xiàn)示例

    c#使用EPPlus將圖片流嵌入到Excel實現(xiàn)示例

    這篇文章主要為大家介紹了c#使用EPPlus將圖片流嵌入到Excel實現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12
  • C#讀取寫入文件的3種方式示例代碼

    C#讀取寫入文件的3種方式示例代碼

    最近操作文件頻繁,所以這篇文章主要給大家介紹了關(guān)于C#讀取寫入文件的3種方式,文中通過實例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用C#具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2022-06-06
  • 在.NET?WebService中跨域CORS問題的解決方案

    在.NET?WebService中跨域CORS問題的解決方案

    在現(xiàn)代的Web應(yīng)用程序開發(fā)中,跨域資源共享(Cross-Origin?Resource?Sharing,?CORS)問題是開發(fā)者經(jīng)常遇到的一個挑戰(zhàn),在這篇博客中,我們將深入探討如何在?.NET?WebService?中解決CORS問題,幫助開發(fā)者順利實現(xiàn)跨域請求,需要的朋友可以參考下
    2024-05-05
  • 淺談C#設(shè)計模式之開放封閉原則

    淺談C#設(shè)計模式之開放封閉原則

    這篇文章主要介紹了淺談C#設(shè)計模式之開放封閉原則,需要的朋友可以參考下
    2014-12-12
  • C#中的IEnumerable簡介及簡單實現(xiàn)實例

    C#中的IEnumerable簡介及簡單實現(xiàn)實例

    這篇文章主要介紹了C#中的IEnumerable簡介及簡單實現(xiàn)實例,本文講解了IEnumerable一些知識并給出了一個簡單的實現(xiàn),需要的朋友可以參考下
    2015-03-03
  • c# 委托詳解

    c# 委托詳解

    本文將通過實例解析對c# 委托進(jìn)行詳細(xì)介紹,具有很好的參考價值,下面跟著小編一起來看下吧
    2017-02-02
  • C#中哈希表(Hashtable)的介紹及簡單用法

    C#中哈希表(Hashtable)的介紹及簡單用法

    在.NET Framework中,Hashtable是System.Collections命名空間提供的一個容器,用于處理和表現(xiàn)類似key/value的鍵值對
    2013-03-03
  • OpenCvSharp圖像的修改和保存以及掩膜操作

    OpenCvSharp圖像的修改和保存以及掩膜操作

    這篇文章主要介紹了OpenCvSharp圖像的修改和保存以及掩膜操作,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • C#導(dǎo)出pdf的實現(xiàn)方法(瀏覽器不預(yù)覽直接下載)

    C#導(dǎo)出pdf的實現(xiàn)方法(瀏覽器不預(yù)覽直接下載)

    這篇文章主要給大家介紹了關(guān)于C#導(dǎo)出pdf的實現(xiàn)方法,實現(xiàn)后瀏覽器不預(yù)覽就可以直接下載,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用C#具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-12-12

最新評論