当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作。直到该线程完成操作,其他线程才能对该内存地址进行操作,而其他线程又处于等待状态,这种时候很容易出现问题

争用条件

如果两个或多个线程访问相同的对象。并且对共享状态的访问没有同步,就会出现争用条件

  1. class StateObject
  2. {
  3. private int state = 5;
  4. public void ChangeState()
  5. {
  6. state++;
  7. if (state == 5)
  8. {
  9. Console.WriteLine("value=5");
  10. }
  11. state = 5;
  12. }
  13. }
  1. public static void ThreadSync_Lock_Example_01()
  2. {
  3. StateObject m = new StateObject();
  4. Thread t1 = new Thread(ChangeState);
  5. t1.Start(m);
  6. Console.ReadKey();
  7. static void ChangeState(object o)
  8. {
  9. StateObject m = o as StateObject;
  10. while (true)
  11. {
  12. m.ChangeState();
  13. }
  14. }
  15. }

此时运行程序是没有输出的,因为StateObject类中state初始值是5,if条件不会进入,随后又将state从新初始化为5

两个线程执行

  1. public static void ThreadSync_Lock_Example_02()
  2. {
  3. StateObject m = new StateObject();
  4. Thread t1 = new Thread(ChangeState);
  5. Thread t2 = new Thread(ChangeState);
  6. t1.Start(m);
  7. t2.Start(m);
  8. Console.ReadKey();
  9. static void ChangeState(object o)
  10. {
  11. StateObject m = o as StateObject;
  12. while (true)
  13. {
  14. m.ChangeState();
  15. }
  16. }
  17. }

此时会不停的打印”value=5”,原因在于:一个线程在判断语句处时,另一个线程可能又将state的值改为了5,而导致输出合法

死锁

  1. class Deadlock
  2. {
  3. static StateObject o1 = new StateObject();
  4. static StateObject o2 = new StateObject();
  5. public static void DeadlockA(object o)
  6. {
  7. lock (o1)
  8. {
  9. Console.WriteLine("我是线程{0},我锁定了对象o1", o);
  10. lock (o2)
  11. {
  12. Console.WriteLine("我是线程{0},我锁定了对象o2", o);
  13. }
  14. }
  15. }
  16. public static void DeadlockB(object o)
  17. {
  18. lock (o2)
  19. {
  20. Console.WriteLine("我是线程{0},我锁定了对象o2", o);
  21. lock (o1)
  22. {
  23. Console.WriteLine("我是线程{0},我锁定了对象o1", o);
  24. }
  25. }
  26. }
  27. }
  1. Thread t1 = new Thread(Deadlock.DeadlockA);
  2. Thread t2 = new Thread(Deadlock.DeadlockB);
  3. t1.Start("t1");
  4. t2.Start("t2");

image.png
t1线程执行DeadlockA()方法顺序锁定o1o2
t2线程执行DeadlockB()方法顺序锁定o2o1
当前结果显示t1线程锁定了o1后,t2线程在t1线程锁定o1后抢占进来,锁定了o2t2在等t1解锁,t1在等t2解锁,都处于挂起状态在等对方解锁,这就形成了死锁,线程将无限等待下去

这个问题应该从一开始就设计好锁定顺序,也可以为锁定义超时时间来处理,保证“上锁”这个操作在一个线程上执行也是避免死锁的方法之一

C#中有多个用于多线程的同步技术

  • lock语句
  • Interlocked类
  • Monitor类
  • SpinLock类
  • WaitHandle类
  • Mutex类
  • Semapphore类
  • Event类
  • Barrier类
  • ReaderWriteLockSlim类

其中lock语句/Interlocked类/Monitor类可用于进程内存的同步,其它几个提供了多进程之间的线程同步

Lock

C#使用lock语句锁定在线程中共享的变量,如果一个线程锁定了变量,另一个线程就必须等待该锁定的解除

  1. static void ChangeState(object o)
  2. {
  3. StateObject m = o as StateObject;
  4. while (true)
  5. {
  6.        //给变量m加锁
  7. lock (m)
  8. {
  9. m.ChangeState();
  10. }
  11. }
  12. }

因为实例的对象也可以用于外部的同步访问,而且不能在类自身控制这种访问,所以应采用SyncRoot模式,创建私有对象,将这个对象用于lock语句

  1. private static object syncRoot= new object();
  2. static void ChangeState(object o)
  3. {
  4. StateObject m = o as StateObject;
  5. while (true)
  6. {
  7. lock (aync)
  8. {
  9. m.ChangeState();
  10. }
  11. }
  12. }

需要注意

  1. lock只能锁定对象,即引用类型,不能锁定值类型
  2. lock不能锁定空值,因为null是不需要被释放的
  3. 不能锁定string类型,虽然它也是引用类型的。因为字符串类型被CLR暂留,这意味着整个程序中任何给定字符串都只有一个实例,具有相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的所有实例
  4. 避免锁定public类型,如果该实例可以被公开访问,则 lock(this) 可能会有问题,因为不受控制的代码也可能会锁定该对象

锁是否必须是静态类型?

如果被锁定的方法是静态的,那么这个锁必须是静态类型。这样就是在全局锁定了该方法,不管该类有多少个实例,都要排队执行

如果被锁定的方法不是静态的,那么不能使用静态类型的锁,因为被锁定的方法是属于实例的。只要该实例调用锁定方法不产生损坏就可以,不同实例间是不需要锁的。这个锁只锁该实例的方法,而不是锁所有实例的方法

  1. class ThreadSafe
  2. {
  3. private static object _locker = new object();
  4. void Go()
  5. {
  6. lock (_locker)
  7. {
  8. ......//共享数据的操作 (Static Method),使用静态锁确保所有实例排队执行
  9. }
  10. }
  11. private object _locker2=new object();
  12. void GoTo()
  13. {
  14. lock(_locker2)
  15. //共享数据的操作,非静态方法,是用非静态锁,确保同一个实例的方法调用者排队执行
  16. }
  17. }

同步对象可以兼作它lock的对象
如:

  1. class ThreadSafe
  2. {
  3. private List <string> _list = new List <string>();
  4. void Test()
  5. {
  6. lock (_list)
  7. {
  8. _list.Add ("Item 1");
  9. }
  10. }
  11. }

Monitors

lock其实是Monitors的简洁写法

  1. lock (syncRoot)
  2. {
  3. m.ChangeState();
  4. }

两者其实是一样的

  1. Monitor.Enter(syncRoot);
  2. try
  3. {
  4. m.ChangeState();
  5. }
  6. finally
  7. {
  8. Monitor.Exit(syncRoot);
  9. }

Mutex

互斥锁是一个互斥的同步对象,同一时间有且仅有一个线程可以获取它。可以实现进程级别上线程的同步

  1. public static void ThreadSync_Mutex_Example_01()
  2. {
  3. for (int i = 0; i < 3; i++)
  4. {
  5. //在不同的线程中调用受互斥锁保护的方法
  6. Thread test = new Thread(MutexMethod);
  7. test.Start();
  8. }
  9. Console.Read();
  10. static void MutexMethod()
  11. {
  12. //实例化一个互斥锁
  13. Mutex mutex = new Mutex();
  14. Console.WriteLine("{0} 请求获取互斥锁", Thread.CurrentThread.ManagedThreadId);
  15. mutex.WaitOne();
  16. Console.WriteLine("{0} 已获取到互斥锁", Thread.CurrentThread.ManagedThreadId);
  17. Console.WriteLine("{0} 准备释放互斥锁", Thread.CurrentThread.ManagedThreadId);
  18. // 释放互斥锁
  19. mutex.ReleaseMutex();
  20. Console.WriteLine("{0} 已经释放互斥锁", Thread.CurrentThread.ManagedThreadId);
  21. }
  22. }

互斥锁的带有三个参数的构造函数

  • initiallyOwned: 如果initiallyOwnedtrue,互斥锁的初始状态就是被所实例化的线程所获取,否则实例化的线程处于未获取状态
  • name:该互斥锁的名字,在操作系统中只有一个命名为name的互斥锁mutex,如果一个线程得到这个name的互斥锁,其他线程就无法得到这个互斥锁了,必须等待那个线程对这个线程释放
  • createNew:如果指定名称的互斥体已经存在就返回false,否则返回true