多線程下解決資源競爭的7種方法

前言

  通常狀況下,只要涉及到多線程編程,程序的複雜性就會顯著上升,性能顯著降低,BUG出現的機率大大提高。程序員

多線程編程本意是將一段程序並行運行,提高數據處理能力,可是因爲大部分狀況下都涉及到共有資源的競爭,因此修改資源編程

對象時必須加鎖處理。可是鎖的實現有不少種方法,下面就來一塊兒瞭解一下在C#語言中幾種鎖的實現與其性能表現。c#

 

1、c#下的幾種鎖的運用方式

一、臨界區,經過對多線程的串行化來訪問公共資源或一段代碼,速度快,適合控制數據訪問。安全

 1         private static object obj = new object();
 2         private static int lockInt;
 3         private static void LockIntAdd()
 4         {
 5             for (var i = 0; i < runTimes; i++)
 6             {
 7                 lock (obj)
 8                 {
 9                     lockInt++;
10                 }
11             }
12         }

你沒看錯,c#中的lock語法就是臨界區(Monitor)的一個語法糖,這大概是90%以上的.net程序員首先想到的鎖,不過大部分人都只是知道多線程

有這麼個語法,不知道實際上是以臨界區的方式處理資源競爭。ide

 

二、互斥量,爲協調共同對一個共享資源的單獨訪問而設計的。oop

c#中有一個Mutex類,就在System.Threading命名空間下,Mutex其實就是互斥量,互斥量不僅僅能處理多線程之間的資源競爭,還能處理性能

進程之間的資源競爭,功能是比較強大的,可是開銷也很大,性能比較低。測試

 1         private static Mutex mutex = new Mutex();
 2         private static int mutexInt;
 3         private static void MutexIntAdd()
 4         {
 5             for (var i = 0; i < runTimes; i++)
 6             {
 7                 mutex.WaitOne();
 8                 mutexInt++;
 9                 mutex.ReleaseMutex();
10             }
11         }

 

三、信號量,爲控制一個具備有限數量用戶資源而設計。atom

 1         private static Semaphore sema = new Semaphore(1, 1);
 2         private static int semaphoreInt;
 3         private static void SemaphoreIntAdd()
 4         {
 5             for (var i = 0; i < runTimes; i++)
 6             {
 7                 sema.WaitOne();
 8                 semaphoreInt++;
 9                 sema.Release();
10             }
11         }

 

四、事   件:用來通知線程有一些事件已發生,從而啓動後繼任務的開始。

 1         public static AutoResetEvent autoResetEvent = new AutoResetEvent(true);
 2         private static int autoResetEventInt;
 3         private static void AutoResetEventIntAdd()
 4         {
 5             for (var i = 0; i < runTimes; i++)
 6             {
 7                 if (autoResetEvent.WaitOne())
 8                 {
 9                     autoResetEventInt++;
10                     autoResetEvent.Set();
11                 }
12             }
13         }

 

五、讀寫鎖,這種鎖容許在有其餘程序正在寫的狀況下讀取資源,因此若是資源容許髒讀,用這個比較合適

 1         private static ReaderWriterLockSlim LockSlim = new ReaderWriterLockSlim();
 2         private static int lockSlimInt;
 3         private static void LockSlimIntAdd()
 4         {
 5             for (var i = 0; i < runTimes; i++)
 6             {
 7                 LockSlim.EnterWriteLock();
 8                 lockSlimInt++;
 9                 LockSlim.ExitWriteLock();
10             }
11         }

 

六、原子鎖,經過原子操做Interlocked.CompareExchange實現「無鎖」競爭

 1         private static int isLock;
 2         private static int ceInt;
 3         private static void CEIntAdd()
 4         {
 5             //long tmp = 0;
 6             for (var i = 0; i < runTimes; i++)
 7             {
 8                 while (Interlocked.CompareExchange(ref isLock, 1, 0) == 1) { Thread.Sleep(1); }
 9 
10                 ceInt++;
11                 Interlocked.Exchange(ref isLock, 0);
12             }
13         }

 

七、原子性操做,這是一種特例,野外原子性操做自己天生線程安全,因此無需加鎖

1         private static int atomicInt;
2         private static void AtomicIntAdd()
3         {
4             for (var i = 0; i < runTimes; i++)
5             {
6                 Interlocked.Increment(ref atomicInt);
7             }
8         }

 

八、不加鎖,若是不加鎖,那多線程下運行結果確定是錯的,這裏貼上來比較一下性能

1         private static int noLockInt;
2         private static void NoLockIntAdd()
3         {
4             for (var i = 0; i < runTimes; i++)
5             {
6                 noLockInt++;
7             }
8         }

 

2、性能測試

一、測試代碼,執行1000,10000,100000,1000000次

 1         private static void Run()
 2         {
 3             var stopwatch = new Stopwatch();
 4             var taskList = new Task[loopTimes];
 5 
 6             // 多線程
 7             Console.WriteLine();
 8             Console.WriteLine($"              線程數:{loopTimes}");
 9             Console.WriteLine($"            執行次數:{runTimes}");
10             Console.WriteLine($"        校驗值應等於:{runTimes * loopTimes}");
11 
12             // AtomicIntAdd
13             stopwatch.Restart();
14             for (var i = 0; i < loopTimes; i++)
15             {
16                 taskList[i] = Task.Factory.StartNew(() => { AtomicIntAdd(); });
17             }
18             Task.WaitAll(taskList);
19             Console.WriteLine($"{GetFormat("AtomicIntAdd")}, 總耗時:{stopwatch.ElapsedMilliseconds}毫秒, 校驗值:{atomicInt}");
20 
21             // CEIntAdd
22             taskList = new Task[loopTimes];
23             stopwatch.Restart();
24 
25             for (var i = 0; i < loopTimes; i++)
26             {
27                 taskList[i] = Task.Factory.StartNew(() => { CEIntAdd(); });
28             }
29             Task.WaitAll(taskList);
30             Console.WriteLine($"{GetFormat("CEIntAdd")}, 總耗時:{stopwatch.ElapsedMilliseconds}毫秒, 校驗值:{ceInt}");
31 
32             // LockIntAdd
33             taskList = new Task[loopTimes];
34             stopwatch.Restart();
35 
36             for (var i = 0; i < loopTimes; i++)
37             {
38                 taskList[i] = Task.Factory.StartNew(() => { LockIntAdd(); });
39             }
40             Task.WaitAll(taskList);
41             Console.WriteLine($"{GetFormat("LockIntAdd")}, 總耗時:{stopwatch.ElapsedMilliseconds}毫秒, 校驗值:{lockInt}");
42 
43             // MutexIntAdd
44             taskList = new Task[loopTimes];
45             stopwatch.Restart();
46 
47             for (var i = 0; i < loopTimes; i++)
48             {
49                 taskList[i] = Task.Factory.StartNew(() => { MutexIntAdd(); });
50             }
51             Task.WaitAll(taskList);
52             Console.WriteLine($"{GetFormat("MutexIntAdd")}, 總耗時:{stopwatch.ElapsedMilliseconds}毫秒, 校驗值:{mutexInt}");
53 
54             // LockSlimIntAdd
55             taskList = new Task[loopTimes];
56             stopwatch.Restart();
57 
58             for (var i = 0; i < loopTimes; i++)
59             {
60                 taskList[i] = Task.Factory.StartNew(() => { LockSlimIntAdd(); });
61             }
62             Task.WaitAll(taskList);
63             Console.WriteLine($"{GetFormat("LockSlimIntAdd")}, 總耗時:{stopwatch.ElapsedMilliseconds}毫秒, 校驗值:{lockSlimInt}");
64 
65             // SemaphoreIntAdd
66             taskList = new Task[loopTimes];
67             stopwatch.Restart();
68 
69             for (var i = 0; i < loopTimes; i++)
70             {
71                 taskList[i] = Task.Factory.StartNew(() => { SemaphoreIntAdd(); });
72             }
73             Task.WaitAll(taskList);
74             Console.WriteLine($"{GetFormat("SemaphoreIntAdd")}, 總耗時:{stopwatch.ElapsedMilliseconds}毫秒, 校驗值:{semaphoreInt}");
75 
76 
77             // AutoResetEventIntAdd
78             taskList = new Task[loopTimes];
79             stopwatch.Restart();
80 
81             for (var i = 0; i < loopTimes; i++)
82             {
83                 taskList[i] = Task.Factory.StartNew(() => { AutoResetEventIntAdd(); });
84             }
85             Task.WaitAll(taskList);
86             Console.WriteLine($"{GetFormat("AutoResetEventIntAdd")}, 總耗時:{stopwatch.ElapsedMilliseconds}毫秒, 校驗值:{autoResetEventInt}");
87 
88             // NoLockIntAdd
89             taskList = new Task[loopTimes];
90             stopwatch.Restart();
91 
92             for (var i = 0; i < loopTimes; i++)
93             {
94                 taskList[i] = Task.Factory.StartNew(() => { NoLockIntAdd(); });
95             }
96             Task.WaitAll(taskList);
97             Console.WriteLine($"{GetFormat("NoLockIntAdd")}, 總耗時:{stopwatch.ElapsedMilliseconds}毫秒, 校驗值:{noLockInt}");
98             Console.WriteLine();
99         }
View Code

二、線程:10

三、線程:50

3、總結

 

1)在各類測試中,不加鎖確定是最快的,因此儘可能避免資源競爭致使加鎖運行

2)在多線程中Interlocked.CompareExchange始終表現出優越的性能,排在第二位

3)第三位lock,臨界區也表現出很好的性能,因此在別人說lock性能低的時候請反駁他

4)第四位是原子性變量(Atomic)操做,不過目前只支持變量的自增自減,適用性不強

5)第五位讀寫鎖(ReaderWriterLockSlim)表現也還能夠,而且支持無所讀,實用性仍是比較好的

6)剩下的信號量、事件、互斥量,這三種性能最差,固然他們有各自的適用範圍,只是在處理資源競爭這方面表現很差

 

over,就這樣吧,睡覺。。。

相關文章
相關標籤/搜索