netcore 併發鎖 多線程中使用SemaphoreSlim

SemaphoreSlim是一個用於同步和限制併發訪問的類,和它類似的還有Semaphore,只是SemaphoreSlim更加的輕量、高效、好用。今天說說它,以及如何使用,在什麼時候去使用,使用它將會帶來什麼優勢。

代碼的業務是:

在多線程下進行數據的統計工作,簡單點的說就是累加數據。

1.首先我們建立一個程序

代碼如下

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
 
namespace ConsoleApp2
{
    class Program
    {
        static int a = 0;
        static async Task Main(string[] args)
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            Task t1 = Task.Run(() =>
            {
                A();
            });
 
            Task t2 = Task.Run(() =>
            {
                B();
            });
            await Task.WhenAll(t1, t2);
            stopwatch.Stop();
            Console.WriteLine("總時間:" + stopwatch.ElapsedMilliseconds);
            Console.WriteLine("總數:" + a);
            Console.ReadLine();
        }
        private static void A()
        {
            for (int i = 0; i < 100_0000; i++)
            {
                a++;
            }
        }
 
        private static void B()
        {
            for (int i = 0; i < 100_0000; i++)
            {
                a++;
            }
        }
 
    }
}

  2.運行結果

 

 

此時需要多運行幾次,會發現,偶爾出現運行的結果不一樣,這就是今天的問題

3.分析結果

從結果看,明顯錯誤了,正確答案是:200 0000,但是第二次的結果是131 6465。我們的業務就是開啓2個線程,一個A方法,一個B方法,分別對a的數據進行累加計算。那麼爲什麼造成這樣的結果呢?

造成這樣的原因就是多線程的問題。

解決方法一:

1.使用lock

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
 
namespace ConsoleApp2
{
    class Program
    {
        static int a = 0;
        static object o = new object();
        static async Task Main(string[] args)
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            Task t1 = Task.Run(() =>
            {
                A();
            });
 
            Task t2 = Task.Run(() =>
            {
                B();
            });
            await Task.WhenAll(t1, t2);
            stopwatch.Stop();
            Console.WriteLine("總時間:" + stopwatch.ElapsedMilliseconds);
            Console.WriteLine("總數:" + a);
            Console.ReadLine();
        }
        private static void A()
        {
            for (int i = 0; i < 100_0000; i++)
            {
                lock (o)
                {
                    a++;
                }
            }
        }
 
        private static void B()
        {
            for (int i = 0; i < 100_0000; i++)
            {
                lock (o)
                {
                    a++;
                }
            }
        }
 
    }
}

2. lock的結果

  

當我們增加lock後,不管運行幾次,結果都是正確的。 

解決方法二:

1.使用SemaphoreSlim

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
 
namespace ConsoleApp2
{
    class Program
    {
        static int a = 0;
        static object o = new object();
        static SemaphoreSlim semaphore = new SemaphoreSlim(1);  //控制訪問線程的數量
        static async Task Main(string[] args)
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            Task t1 = Task.Run(() =>
            {
                A();
            });
 
            Task t2 = Task.Run(() =>
            {
                B();
            });
            await Task.WhenAll(t1, t2);
            stopwatch.Stop();
            Console.WriteLine("總時間:" + stopwatch.ElapsedMilliseconds);
            Console.WriteLine("總數:" + a);
            Console.ReadLine();
        }
        private static void A()
        {
            for (int i = 0; i < 100_0000; i++)
            {
                semaphore.Wait();
                //lock (o)
                //{
                a++;
                //}
                semaphore.Release();
            }
        }
 
        private static void B()
        {
            for (int i = 0; i < 100_0000; i++)
            {
                semaphore.Wait();
                //lock (o)
                //{
                a++;
                //}
                semaphore.Release();
            }
        }
 
    }
}

2.SemaphoreSlim的效果

 

當我們增加SemaphoreSlim後,不管運行幾次,結果都是正確的。

4.我們對比方法一和方法二發現,他們的結果都是一樣的,但是lock似乎比SemaphoreSlim更加的高效,是的,lock解決此業務的確比SemaphoreSlim高效。但是lock能幹的事,SemaphoreSlim肯定能幹,SemaphoreSlim能幹的事,lock不一定能幹。

5.SemaphoreSlim的使用

SemaphoreSlim使用的範圍非常的廣,可以限制訪問資源的線程數,例如限制一個資源最多5個線程可以同時訪問

using System.Threading;
 
class Program
{
    static SemaphoreSlim semaphore = new SemaphoreSlim(5); // 允許最多5個線程同時訪問資源
 
    static void Main()
    {
        for (int i = 0; i < 10; i++)
        {
            Task.Run(DoWork);
        }
        Console.ReadLine();
    }
 
    static async Task DoWork()
    {
        await semaphore.WaitAsync(); // 等待許可
        try
        {
            Console.WriteLine($"線程 {Thread.CurrentThread.ManagedThreadId} 開始工作");
            await Task.Delay(1000); // 模擬耗時操作
            Console.WriteLine($"線程 {Thread.CurrentThread.ManagedThreadId} 結束工作");
        }
        finally
        {
            semaphore.Release(); // 釋放許可
        }
    }
}

 

此時DoWork()這個方法,最多同時只有5個線程訪問,當改成1個,就是按照順序進行了,和lock的使用是一樣的

6.總結

如果需要確保同一時間只有一個線程訪問某資源(此案例指的就是變量a),那麼可以使用Lock,也可以使用SemaphoreSlim;如果需要控制同時訪問資源的線程數量,並且需要更復雜的信號量操作,那麼可以使用SemaphoreSlim。總之,使用Lock還是SemaphoreSlim,都是根據具體業務而定。

拓展:

當我們在第1步,只需要增加一句話,不增加lock和SemaphoreSlim,依然可以使得計算的結果準確,那就是增加

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
 
namespace ConsoleApp2
{
    class Program
    {
        static int a = 0;
        static async Task Main(string[] args)
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            Task t1 = Task.Run(() =>
            {
                A();
            });
 
            Task t2 = Task.Run(() =>
            {
                B();
            });
            await Task.WhenAll(t1, t2);
            stopwatch.Stop();
            Console.WriteLine("總時間:" + stopwatch.ElapsedMilliseconds);
            Console.WriteLine("總數:" + a);
            Console.ReadLine();
        }
        private static void A()
        {
            for (int i = 0; i < 10_0000; i++)
            {
                a++;
                Console.WriteLine(a);
            }
        }
 
        private static void B()
        {
            for (int i = 0; i < 10_0000; i++)
            {
                a++;
                Console.WriteLine(a);
            }
        }
 
    }
}

效果

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章