当多个线程在并发的时候,难免会碰到相互冲突的事情,比如最经典的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 //注意,这里使用的是同一个资源对象obj15 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 Jack35 {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 John66 {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