您的位置:首页 > 其它

5天不再惧怕多线程——第二天 锁机制

2016-04-16 17:00 429 查看


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 }


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: