亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

深入了解C#多線程安全

 更新時(shí)間:2021年12月22日 09:29:54   作者:小六公子  
使用多線程無法避免的一個(gè)問題就是多線程安全。那什么是多線程安全?如何解決多線程安全?本文將通過一些簡(jiǎn)單的例子為大家詳細(xì)介紹一下多線程相關(guān)的問題,感興趣的可以了解一下

前面兩篇文章,分別簡(jiǎn)述了多線程的使用和發(fā)展歷程,但是使用多線程無法避免的一個(gè)問題就是多線程安全。那什么是多線程安全?如何解決多線程安全?本文主要通過一些簡(jiǎn)單的小例子,簡(jiǎn)述多線程相關(guān)的問題,僅供學(xué)習(xí)分享使用,如有不足之處,還請(qǐng)指正。

什么是多線程安全?

一段程序,單線程和多線程執(zhí)行結(jié)果不一致,就表示存在多線程安全問題,即多線程不安全。

多線程安全示例

1. 多線程不安全示例1

假如我們有一個(gè)需求,需要輸出5個(gè)線程,且線程序號(hào)按0-4命名,我們編寫代碼如下:

private void btnTask1_Click(object sender, EventArgs e)
{
    Console.WriteLine("【開始】**************線程不安全示例btnTask1_Click**************");

    for (int i = 0; i < 5; i++)
    {
        Task.Run(() =>
        {
            Console.WriteLine($"【BEGIN】**************這是第 {i} 個(gè)線程,線程ID={Thread.CurrentThread.ManagedThreadId}**************");
            Thread.Sleep(2000);
            Console.WriteLine($"【 END 】**************這是第 {i} 個(gè)線程,線程ID={Thread.CurrentThread.ManagedThreadId}**************");
        });
    }

    Console.WriteLine("【結(jié)束】**************線程不安全示例btnTask1_Click**************");
}

然后運(yùn)行示例,如下所示:

通過對(duì)以上示例進(jìn)行分析,得出結(jié)論如下:

1.在for循環(huán)中,啟動(dòng)的5個(gè)線程,線程序號(hào)都是5,并沒有按照我們預(yù)期的結(jié)果【0,1,2,3,4】進(jìn)行輸出。

2.經(jīng)過分析發(fā)現(xiàn),因?yàn)閒or循環(huán)中,i是同一個(gè)變量,線程啟動(dòng)是異步進(jìn)行的,存在延遲,當(dāng)線程啟動(dòng)時(shí),for循環(huán)已經(jīng)結(jié)束,i的值為5,所以才導(dǎo)致線程序號(hào)和預(yù)期不一致。

為了解決上述問題,可以通過引入局部變量來解決,即每次循環(huán)聲明一個(gè)變量,循環(huán)5次,存在5個(gè)變量,則相互之間不會(huì)覆蓋。如下所示:

private void btnTask1_Click(object sender, EventArgs e)
{
    Console.WriteLine("【開始】**************線程不安全示例btnTask1_Click**************");

    for (int i = 0; i < 5; i++)
    {
        int k = i;
        Task.Run(() =>
        {
            Console.WriteLine($"【BEGIN】**************這是第 {k} 個(gè)線程,線程ID={Thread.CurrentThread.ManagedThreadId}**************");
            Thread.Sleep(2000);
            Console.WriteLine($"【 END 】**************這是第 {k} 個(gè)線程,線程ID={Thread.CurrentThread.ManagedThreadId}**************");
        });
    }

    Console.WriteLine("【結(jié)束】**************線程不安全示例btnTask1_Click**************");
}

運(yùn)行優(yōu)化后的示例,如下所示:

通過運(yùn)行示例發(fā)現(xiàn),局部變量可以解決相應(yīng)的問題。

2. 多線程不安全示例2

假如我們有一個(gè)需求:將0到200增加到一個(gè)列表中,采用多線程來實(shí)現(xiàn),如下所示:

private void btnTask2_Click(object sender, EventArgs e)
{
    Console.WriteLine("【開始】**************線程不安全示例btnTask1_Click**************");
    List<int> list = new List<int>();
    List<Task> tasks = new List<Task>();
    for (int i = 0; i < 200; i++)
    {
        tasks.Add( Task.Run(() =>
        {
            list.Add(i);
        }));
    }
    Task.WaitAll(tasks.ToArray());
    string res = string.Join(",", list);
    Console.WriteLine($"列表長(zhǎng)度: {list.Count} ,列表內(nèi)容:{res}");
    Console.WriteLine("【結(jié)束】**************線程不安全示例btnTask1_Click**************");
}

通過運(yùn)行示例,如下所示:

通過對(duì)以上示例進(jìn)行分析,得出結(jié)論如下:

1.列表的記錄條數(shù)不對(duì),會(huì)少。

2.列表的元素內(nèi)容與預(yù)期的內(nèi)容不一致。

針對(duì)上述問題,采用中間局部變量的方式,可以解決嗎?不妨一試,修改后的 代碼如下:

private void btnTask2_Click(object sender, EventArgs e)
{
    Console.WriteLine("【開始】**************線程不安全示例btnTask1_Click**************");
    List<int> list = new List<int>();
    List<Task> tasks = new List<Task>();
    for (int i = 0; i < 200; i++)
    {
        int k = i;
        tasks.Add( Task.Run(() =>
        {
            list.Add(k);
        }));
    }
    Task.WaitAll(tasks.ToArray());
    string res = string.Join(",", list);
    Console.WriteLine($"列表長(zhǎng)度: {list.Count} ,列表內(nèi)容:{res}");
    Console.WriteLine("【結(jié)束】**************線程不安全示例btnTask1_Click**************");
}

運(yùn)行優(yōu)化示例,如下所示:

通過運(yùn)行上述示例,得出結(jié)論如下:

1.列表長(zhǎng)度依然不對(duì),會(huì)小于實(shí)際單一線程的長(zhǎng)度。注意:多線程列表長(zhǎng)度不是一定會(huì)小于單一線程運(yùn)行時(shí)列表長(zhǎng)度,只是存在概率,即多個(gè)線程存在同時(shí)寫入一個(gè)位置的概率。

2.列表內(nèi)容,采用局部變量,可以解決部分問題。

由此可以得出List不是線程安全的數(shù)據(jù)類型。

加鎖lock

針對(duì)多線程的不安全問題,可以通過加鎖進(jìn)行解決,加鎖的目的:在任意時(shí)刻,加鎖塊都之允許一個(gè)線程訪問。

加鎖原理

lock實(shí)際是一個(gè)語法糖,實(shí)際效果等同于Monitor。鎖定的是引用對(duì)象的一個(gè)內(nèi)存地址引用。所以鎖定對(duì)象不可以是值類型,也不可以是null,只能是引用類型。

lock對(duì)象的標(biāo)準(zhǔn)寫法:默認(rèn)情況下,鎖對(duì)象是私有,靜態(tài),只讀,引用對(duì)象。如下所示:

/// <summary>
/// 定義一個(gè)鎖對(duì)象
/// </summary>
private static readonly object obj = new object();

然后優(yōu)化程序,如下所示:

private void btnTask2_Click(object sender, EventArgs e)
{
    Console.WriteLine("【開始】**************線程不安全示例btnTask1_Click**************");
    List<int> list = new List<int>();
    List<Task> tasks = new List<Task>();
    for (int i = 0; i < 200; i++)
    {
        int k = i;
        tasks.Add( Task.Run(() =>
        {
            lock (obj)
            {
                list.Add(k);
            }
        }));
    }
    Task.WaitAll(tasks.ToArray());
    string res = string.Join(",", list);
    Console.WriteLine($"列表長(zhǎng)度: {list.Count} ,列表內(nèi)容:{res}");
    Console.WriteLine("【結(jié)束】**************線程不安全示例btnTask1_Click**************");
}

運(yùn)行優(yōu)化后的示例,如下所示:

通過對(duì)上述示例進(jìn)行分析,得出結(jié)論如下:

1.加鎖后,列表在多線程下也變成安全,符合預(yù)期的要求。

2.但是由于加鎖的原因,同一時(shí)刻,只能由一個(gè)線程進(jìn)入,其他線程就會(huì)等待,所以多線程也變成了單線程。

為何鎖對(duì)象要用私有類型?

標(biāo)準(zhǔn)寫法,鎖對(duì)象是私有類型,目的是為了避免鎖對(duì)象被其他線程使用,如果被使用,則會(huì)相互阻塞,如下所示:

假如,現(xiàn)在有一個(gè)鎖對(duì)象,在TestLock中使用,如下所示:

public class TestLock
{
    public static readonly object Obj = new object();

    public void Show()
    {

        Console.WriteLine("【開始】**************線程示例Show**************");

        for (int i = 0; i < 5; i++)
        {
            int k = i;
            Task.Run(() =>
            {
                lock (Obj)
                {
                    Console.WriteLine($"【BEGIN】*********T*****這是第 {k} 個(gè)線程,線程ID={Thread.CurrentThread.ManagedThreadId}**************");
                    Thread.Sleep(2000);
                    Console.WriteLine($"【 END 】*********T*****這是第 {k} 個(gè)線程,線程ID={Thread.CurrentThread.ManagedThreadId}**************");
                }
            });
        }

        Console.WriteLine("【結(jié)束】**************線程示例Show**************");
    }
}

同時(shí)在FrmMain中使用,如下所示:

private void btnTask3_Click(object sender, EventArgs e)
{
    Console.WriteLine("【開始】**************線程示例btnTask3_Click**************");
    //類對(duì)象中多線程
    TestLock.Show();
    //主方法中多線程
    for (int i = 0; i < 5; i++)
    {
        int k = i;
        Task.Run(() =>
        {
            lock (TestLock.Obj)
            {
                Console.WriteLine($"【BEGIN】*********M*****這是第 {k} 個(gè)線程,線程ID={Thread.CurrentThread.ManagedThreadId}**************");
                Thread.Sleep(2000);
                Console.WriteLine($"【 END 】*********M*****這是第 {k} 個(gè)線程,線程ID={Thread.CurrentThread.ManagedThreadId}**************");
            }
        });
    }

    Console.WriteLine("【結(jié)束】**************線程示例btnTask3_Click**************");
}

運(yùn)行上述示例,如下所示:

通過上述示例,得出結(jié)論如下:

1.T和M是成對(duì)相鄰,且各代碼塊交互出現(xiàn)。

2.多個(gè)代碼塊,共用一把鎖,是會(huì)相互阻塞的。這也是為啥不建議使用public修飾符的原因,避免被不恰當(dāng)?shù)募渔i。

如果使用不同的鎖對(duì)象,多個(gè)代碼塊之間是可以并發(fā)的【T和M是不成對(duì),且不相鄰出現(xiàn),但是有同一代碼塊的內(nèi)部順序】,效果如下:

為什么鎖對(duì)象要用static類型?

假如對(duì)象不是static類型,那么鎖對(duì)象就是對(duì)象屬性,不同的對(duì)象之間是相互獨(dú)立的,所以不同通對(duì)象調(diào)用相同的方法,就會(huì)存在并發(fā)的問題,如下所示:

修改TestLock代碼【去掉static】,如下所示:

public class TestLock
{
    public  readonly object Obj = new object();

    public  void Show(string name)
    {

        Console.WriteLine("【開始】**************線程示例Show--{0}**************",name);

        for (int i = 0; i < 5; i++)
        {
            int k = i;
            Task.Run(() =>
            {
                lock (Obj)
                {
                    Console.WriteLine($"【BEGIN】*********T*****這是第 {k}--{name} 個(gè)線程,線程ID={Thread.CurrentThread.ManagedThreadId}**************");
                    Thread.Sleep(2000);
                    Console.WriteLine($"【 END 】*********T*****這是第 {k}--{name} 個(gè)線程,線程ID={Thread.CurrentThread.ManagedThreadId}**************");
                }
            });
        }

        Console.WriteLine("【結(jié)束】**************線程示例Show--{0}**************",name);
    }
}

聲明兩個(gè)對(duì)象,分別調(diào)用Show方法,如下所示:

private void btnTask4_Click(object sender, EventArgs e)
{
    Console.WriteLine("【開始】**************線程示例btnTask3_Click**************");
    TestLock testLock1 = new TestLock();
    testLock1.Show("first");

    TestLock testLock2 = new TestLock();
    testLock2.Show("second");
    Console.WriteLine("【結(jié)束】**************線程示例btnTask3_Click**************");
}

測(cè)試示例,如下所示:

通過以上示例,得出結(jié)論如下:

非靜態(tài)鎖對(duì)象,只在當(dāng)前對(duì)象內(nèi)部進(jìn)行允許同一時(shí)刻只有一個(gè)線程進(jìn)入,但是多個(gè)對(duì)象之間,是相互并發(fā),相互獨(dú)立的。所以建議鎖對(duì)象為static對(duì)象。

加鎖鎖定的是什么?

在lock模式下,鎖定的是內(nèi)存引用地址,而不是鎖定的對(duì)象的值。假如將Form的鎖對(duì)象的類型改為字符串,如下所示:

/// <summary>
/// 定義一個(gè)鎖對(duì)象
/// </summary>
private static readonly string obj = "花無缺";

同時(shí)TestLock類的鎖對(duì)象也改為字符串,如下所示:

public class TestLock
{
    private static  readonly string obj = "花無缺";

    public static  void Show(string name)
    {

        Console.WriteLine("【開始】**************線程示例Show--{0}**************",name);

        for (int i = 0; i < 5; i++)
        {
            int k = i;
            Task.Run(() =>
            {
                lock (obj)
                {
                    Console.WriteLine($"【BEGIN】*********T*****這是第 {k}--{name} 個(gè)線程,線程ID={Thread.CurrentThread.ManagedThreadId}**************");
                    Thread.Sleep(2000);
                    Console.WriteLine($"【 END 】*********T*****這是第 {k}--{name} 個(gè)線程,線程ID={Thread.CurrentThread.ManagedThreadId}**************");
                }
            });
        }

        Console.WriteLine("【結(jié)束】**************線程示例Show--{0}**************",name);
    }
}

運(yùn)行上述示例,結(jié)果如下:

通過上述示例,得出結(jié)論如下:

1.字符串是一種特殊的鎖類型,如果字符串的值一致,則認(rèn)為是同一個(gè)鎖對(duì)象,不同對(duì)象之間會(huì)進(jìn)行阻塞。因?yàn)閟tring類型是享元的,在內(nèi)存堆里面只有一個(gè)花無缺。

2.如果是其他類型,則是不同的鎖對(duì)象,是可以相互并發(fā)的。

3.說明鎖定的是內(nèi)存引用地址,而非鎖定對(duì)象的值。

泛型鎖對(duì)象

如果TestLock為泛型類,如下所示:

1 public class TestLock<T>
 2 {
 3     private static? readonly object obj = new object(); 4 
 5     public static  void Show(string name)
 6     {
 7 
 8         Console.WriteLine("【開始】**************線程示例Show--{0}**************",name);
 9 
10         for (int i = 0; i < 5; i++)
11         {
12             int k = i;
13             Task.Run(() =>
14             {
15                 lock (obj)
16                 {
17                     Console.WriteLine($"【BEGIN】*********T*****這是第 {k}--{name} 個(gè)線程,線程ID={Thread.CurrentThread.ManagedThreadId}**************");
18                     Thread.Sleep(2000);
19                     Console.WriteLine($"【 END 】*********T*****這是第 {k}--{name} 個(gè)線程,線程ID={Thread.CurrentThread.ManagedThreadId}**************");
20                 }
21             });
22         }
23 
24         Console.WriteLine("【結(jié)束】**************線程示例Show--{0}**************",name);
25     }
26 }

那么在調(diào)用時(shí),會(huì)相互阻塞嗎?調(diào)用代碼如下:

private void btnTask5_Click(object sender, EventArgs e)
{
    Console.WriteLine("【開始】**************線程示例btnTask5_Click**************");
    TestLock<int>.Show("AA");
    TestLock<string>.Show("BB");
    Console.WriteLine("【結(jié)束】**************線程示例btnTask5_Click**************");
}

運(yùn)行上述示例,如下所示:

通過分析上述示例,得出結(jié)論如下所示:

1.對(duì)于泛型類,不同類型參數(shù)之間是可以相互并發(fā)的,因?yàn)榉盒皖愥槍?duì)不同類型參數(shù)會(huì)編譯成不同的類,那對(duì)應(yīng)的鎖對(duì)象,會(huì)變成不同的引用類型。

2.如果鎖對(duì)象為字符串類型,則也是會(huì)相互阻塞的,只是因?yàn)樽址窍碓J健?/p>

3.泛型T的不同,會(huì)編譯成不同的副本。

遞歸加鎖

如果在遞歸函數(shù)中進(jìn)行加鎖,會(huì)造成死鎖嗎?示例代碼如下:

private void btnTask6_Click(object sender, EventArgs e)
{
    Console.WriteLine("【開始】**************線程示例btnTask6_Click**************");
    this.add(1);
    Console.WriteLine("【結(jié)束】**************線程示例btnTask6_Click**************");
}

private int num = 0;

private void add(int index) {
    this.num++;
    Task.Run(()=> {
        lock (obj)
        {
            Console.WriteLine($"【BEGIN】**************這是第 {num} 個(gè)線程,線程ID={Thread.CurrentThread.ManagedThreadId}**************");
            Thread.Sleep(2000);
            Console.WriteLine($"【 END 】**************這是第 {num} 個(gè)線程,線程ID={Thread.CurrentThread.ManagedThreadId}**************");

            if (num < 5)
            {
                this.add(index);
            }
        }
    });
}

運(yùn)行上述示例,如下所示:

通過運(yùn)行上述示例,得出結(jié)論如下:

在遞歸函數(shù)中進(jìn)行加鎖,會(huì)進(jìn)行阻塞等待,但是不會(huì)造成死鎖。?

以上就是深入了解C#多線程安全的詳細(xì)內(nèi)容,更多關(guān)于C#多線程安全的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 理解C#生成驗(yàn)證碼的過程

    理解C#生成驗(yàn)證碼的過程

    這篇文章主要介紹了C#生成驗(yàn)證碼的過程,通過實(shí)例分析C#驗(yàn)證碼的生成原理,感興趣的小伙伴們可以參考一下
    2016-03-03
  • c#掃描圖片去黑邊(掃描儀去黑邊)

    c#掃描圖片去黑邊(掃描儀去黑邊)

    最近項(xiàng)目遇到一個(gè)問題,需要對(duì)掃描出來的圖片進(jìn)行去除黑邊,下面是實(shí)現(xiàn)代碼,需要的朋友可以參考下
    2014-03-03
  • C#利用Spire.Pdf包實(shí)現(xiàn)為PDF添加數(shù)字簽名

    C#利用Spire.Pdf包實(shí)現(xiàn)為PDF添加數(shù)字簽名

    Spire.PDF for .NET 是一款專業(yè)的基于.NET平臺(tái)的PDF文檔控制組件。它能夠讓開發(fā)人員在不使用Adobe Acrobat和其他外部控件的情況下,運(yùn)用.NET 應(yīng)用程序創(chuàng)建,閱讀,編寫和操縱PDF 文檔。本文將利用其實(shí)現(xiàn)添加數(shù)字簽名,需要的可以參考一下
    2022-08-08
  • C# Dynamic之:ExpandoObject,DynamicObject,DynamicMetaOb的應(yīng)用(下)

    C# Dynamic之:ExpandoObject,DynamicObject,DynamicMetaOb的應(yīng)用(下)

    本篇文章是對(duì)C#中ExpandoObject,DynamicObject,DynamicMetaOb的應(yīng)用進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
    2013-05-05
  • C#職責(zé)鏈模式實(shí)例詳解

    C#職責(zé)鏈模式實(shí)例詳解

    這篇文章主要介紹了C#職責(zé)鏈模式,以實(shí)例形式完整分析了C#職責(zé)鏈模式的相關(guān)技巧與實(shí)現(xiàn)方法,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-07-07
  • C#實(shí)現(xiàn)多線程編程的簡(jiǎn)單案例

    C#實(shí)現(xiàn)多線程編程的簡(jiǎn)單案例

    這篇文章介紹了C#實(shí)現(xiàn)多線程編程的簡(jiǎn)單案例,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-04-04
  • C#判斷頁面中的多個(gè)文本框輸入值是否有重復(fù)的實(shí)現(xiàn)方法

    C#判斷頁面中的多個(gè)文本框輸入值是否有重復(fù)的實(shí)現(xiàn)方法

    這篇文章主要介紹了C#判斷頁面中的多個(gè)文本框輸入值是否有重復(fù)的實(shí)現(xiàn)方法,是一個(gè)非常簡(jiǎn)單實(shí)用的技巧,需要的朋友可以參考下
    2014-10-10
  • C# DialogResult用法案例詳解

    C# DialogResult用法案例詳解

    這篇文章主要介紹了C# DialogResult用法案例詳解,本篇文章通過簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • 如何利用Jenkins + TFS為.Net Core實(shí)現(xiàn)持續(xù)集成/部署詳解

    如何利用Jenkins + TFS為.Net Core實(shí)現(xiàn)持續(xù)集成/部署詳解

    這篇文章主要給大家介紹了關(guān)于如何利用Jenkins + TFS為.Net Core實(shí)現(xiàn)持續(xù)集成/部署的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-05-05
  • C#加密app.config中連接字符串的方法

    C#加密app.config中連接字符串的方法

    這篇文章主要介紹了C#加密app.config中連接字符串的方法,涉及C#配置文件加密的相關(guān)實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-07-07

最新評(píng)論