【轉載】5天不再懼怕多線程——第二天 鎖機制
當多個線程在並發的時候,難免會碰到相互沖突的事情,比如最經典的ATM機的問題,並發不可怕,可怕的是我們沒有能力控制。
線程以我的理解可以分為三種
① 鎖。
② 互斥。
③ 信號。
好,這一篇主要整理“鎖”,C#提供了2種手工控制的鎖
一: Monitor類
這個算是實現鎖機制的純正類,在鎖定的臨界區中只允許讓一個線程訪問,其他線程排隊等待。主要整理為2組方法。
1:Monitor.Enter和Monitor.Exit
微軟很照護我們,給了我們語法糖Lock,對的,語言糖確實減少了我們不必要的勞動並且讓代碼更可觀,但是如果我們要精細的
控制,則必須使用原生類,這裏要註意一個問題就是“鎖住什麽”的問題,一般情況下我們鎖住的都是靜態對象,我們知道靜態對象
屬於類級別,當有很多線程共同訪問的時候,那個靜態對象對多個線程來說是一個,不像實例字段會被認為是多個。
不加鎖的情況:
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 for (int i = 0; i < 10; i++)
6 {
7 Thread t = new Thread(Run);
8
9 t.Start();
10 }
11 }
12
13 //資源
14 static object obj = new object();
15
16 static int count = 0;
17
18 static void Run()
19 {
20 Thread.Sleep(10);
21
22 Console.WriteLine("當前數字:{0}", ++count);
23 }
24 }
加鎖的情況:
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 for (int i = 0; i < 10; i++)
6 {
7 Thread t = new Thread(Run);
8
9 t.Start();
10 }
11 }
12
13 //資源
14 static object obj = new object();
15
16 static int count = 0;
17
18 static void Run()
19 {
20 Thread.Sleep(10);
21
22 //進入臨界區
23 Monitor.Enter(obj);
24
25 Console.WriteLine("當前數字:{0}", ++count);
26
27 //退出臨界區
28 Monitor.Exit(obj);
29 }
30 }
2:Monitor.Wait和Monitor.Pulse
首先這兩個方法是成對出現,通常使用在Enter,Exit之間。
Wait: 暫時的釋放資源鎖,然後該線程進入”等待隊列“中,那麽自然別的線程就能獲取到資源鎖。
Pulse: 喚醒“等待隊列”中的線程,那麽當時被Wait的線程就重新獲取到了鎖。
這裏我們是否註意到了兩點:
① 可能A線程進入到臨界區後,需要B線程做一些初始化操作,然後A線程繼續幹剩下的事情。
② 用上面的兩個方法,我們可以實現線程間的彼此通信。
下面舉個例子來模擬兩個人的對話。
1 using System;
2 using System.Collections.Generic;
3 using System.Text;
4 using System.Threading;
5
6 namespace Test
7 {
8 public class Program
9 {
10 public static void Main(string[] args)
11 {
12 LockObj obj = new LockObj();
13
14 //註意,這裏使用的是同一個資源對象obj
15 Jack jack = new Jack(obj);
16 John john = new John(obj);
17
18 Thread t1 = new Thread(new ThreadStart(jack.Run));
19 Thread t2 = new Thread(new ThreadStart(john.Run));
20
21 t1.Start();
22 t1.Name = "Jack";
23
24 t2.Start();
25 t2.Name = "John";
26
27 Console.ReadLine();
28 }
29 }
30
31 //鎖定對象
32 public class LockObj { }
33
34 public class Jack
35 {
36 private LockObj obj;
37
38 public Jack(LockObj obj)
39 {
40 this.obj = obj;
41 }
42
43 public void Run()
44 {
45 Monitor.Enter(this.obj);
46
47 Console.WriteLine("{0}:我已進入茅廁。", Thread.CurrentThread.Name);
48
49 Console.WriteLine("{0}:擦,太臭了,我還是撤!", Thread.CurrentThread.Name);
50
51 //暫時的釋放鎖資源
52 Monitor.Wait(this.obj);
53
54 Console.WriteLine("{0}:兄弟說的對,我還是進去吧。", Thread.CurrentThread.Name);
55
56 //喚醒等待隊列中的線程
57 Monitor.Pulse(this.obj);
58
59 Console.WriteLine("{0}:拉完了,真舒服。", Thread.CurrentThread.Name);
60
61 Monitor.Exit(this.obj);
62 }
63 }
64
65 public class John
66 {
67 private LockObj obj;
68
69 public John(LockObj obj)
70 {
71 this.obj = obj;
72 }
73
74 public void Run()
75 {
76 Monitor.Enter(this.obj);
77
78 Console.WriteLine("{0}:直奔茅廁,兄弟,你還是進來吧,小心憋壞了!",
79 Thread.CurrentThread.Name);
80
81 //喚醒等待隊列中的線程
82 Monitor.Pulse(this.obj);
83
84 Console.WriteLine("{0}:嘩啦啦....", Thread.CurrentThread.Name);
85
86 //暫時的釋放鎖資源
87 Monitor.Wait(this.obj);
88
89 Console.WriteLine("{0}:拉完了,真舒服。", Thread.CurrentThread.Name);
90
91 Monitor.Exit(this.obj);
92 }
93 }
94 }
二:ReaderWriterLock類
先前也知道,Monitor實現的是在讀寫兩種情況的臨界區中只可以讓一個線程訪問,那麽如果業務中存在”讀取密集型“操作,就
好比數據庫一樣,讀取的操作永遠比寫入的操作多。針對這種情況,我們使用Monitor的話很吃虧,不過沒關系,ReadWriterLock
就很牛X,因為實現了”寫入串行“,”讀取並行“。
ReaderWriteLock中主要用3組方法:
<1> AcquireWriterLock: 獲取寫入鎖。
ReleaseWriterLock:釋放寫入鎖。
<2> AcquireReaderLock: 獲取讀鎖。
ReleaseReaderLock:釋放讀鎖。
<3> UpgradeToWriterLock:將讀鎖轉為寫鎖。
DowngradeFromWriterLock:將寫鎖還原為讀鎖。
下面就實現一個寫操作,三個讀操作,要知道這三個讀操作是並發的。
1 namespace Test
2 {
3 class Program
4 {
5 static List<int> list = new List<int>();
6
7 static ReaderWriterLock rw = new System.Threading.ReaderWriterLock();
8
9 static void Main(string[] args)
10 {
11 Thread t1 = new Thread(AutoAddFunc);
12
13 Thread t2 = new Thread(AutoReadFunc);
14
15 t1.Start();
16
17 t2.Start();
18
19 Console.Read();
20 }
21
22 /// <summary>
23 /// 模擬3s插入一次
24 /// </summary>
25 /// <param name="num"></param>
26 public static void AutoAddFunc()
27 {
28 //3000ms插入一次
29 Timer timer1 = new Timer(new TimerCallback(Add), null, 0, 3000);
30 }
31
32 public static void AutoReadFunc()
33 {
34 //1000ms自動讀取一次
35 Timer timer1 = new Timer(new TimerCallback(Read), null, 0, 1000);
36 Timer timer2 = new Timer(new TimerCallback(Read), null, 0, 1000);
37 Timer timer3 = new Timer(new TimerCallback(Read), null, 0, 1000);
38 }
39
40 public static void Add(object obj)
41 {
42 var num = new Random().Next(0, 1000);
43
44 //寫鎖
45 rw.AcquireWriterLock(TimeSpan.FromSeconds(30));
46
47 list.Add(num);
48
49 Console.WriteLine("我是線程{0},我插入的數據是{1}。", Thread.CurrentThread.ManagedThreadId, num);
50
51 //釋放鎖
52 rw.ReleaseWriterLock();
53 }
54
55 public static void Read(object obj)
56 {
57 //讀鎖
58 rw.AcquireReaderLock(TimeSpan.FromSeconds(30));
59
60 Console.WriteLine("我是線程{0},我讀取的集合為:{1}",
61 Thread.CurrentThread.ManagedThreadId, string.Join(",", list));
62 //釋放鎖
63 rw.ReleaseReaderLock();
64 }
65 }
66 }
轉載地址:http://www.cnblogs.com/huangxincheng/archive/2012/03/14/2397068.html
【轉載】5天不再懼怕多線程——第二天 鎖機制