C# lock 自旋锁,互斥锁,混合锁,读写锁介绍
c# 并行编程、多线程开发中,经常要用到线程锁,so, 看了许多文章,想总结一下,供自己理解记忆,以及园丁们参考使用,理解的不怎么全面,勿喷!在多线程环境中,多个线程可能会同时访问同一个资源,为了避免访问发生冲突,可以根据访问的复杂程度采取不同的措施,原子操作适用于简单的单个操作,无锁算法适用于相对简单的一连串操作,而线程锁适用于复杂的一连串操作
1.lock锁的解释和用法
官方MSDN的说法:lock 关键字可确保当一个线程位于代码的临界区时,另一个线程不会进入该临界区。 如果其他线程尝试进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。lock 关键字在块的开始处调用 Enter,而在块的结尾处调用 Exit。 ThreadInterruptedException 引发,如果 Interrupt 中断等待输入 lock 语句的线程。通常,应避免锁定 public 类型,否则实例将超出代码的控制范围。
1 private static readonly object objlock = new object(); 2 lock (objlock ) 3 { 4 //要执行的代码逻辑 5 }
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading; 6 using System.Threading.Tasks; 7 8 namespace LockTest 9 { 10 class Program 11 { 12 static void Main(string[] args) 13 { 14 TestLock testlock = new TestLock(); 15 Thread th = new Thread(() => 16 { 17 //模拟死锁:造成死锁,使lock无法释放,在i=5时,跳出死循环,释放lock 18 testlock.DoWorkWithLock(); 19 }); 20 th.Start(); 21 Thread.Sleep(1000); 22 Thread th2 = new Thread(() => 23 { 24 //这个地方你可能会有疑惑,但存在这种情况,比如你封装的dll,对其它开发人员不是可见的 25 //开发人员很有可能在他的逻辑中,加上一个lock保证方法同时被一个线程调用,但这时有其它的线程正在调用该方法, 26 //但并没有释放,死锁了,那么在这里就不会被执行,除非上面的线程释放了lock锁定的对象。这里的lock也可以理解为一个标识,线程1被锁定的对象 27 //是否已经被释放, 28 //如果没有释放,则无法继续访问lock块中的代码。 29 lock (testlock) 30 { 31 // 如果该对象中lock(this)不释放(testlock与this指的是同一个对象),则其它线程如果调用该方法,则会出现直到lock(this)释放后才能继续调用。 32 testlock.MotherCallYouDinner(); 33 testlock.DoWorkWithLock(); 34 } 35 }); 36 th2.Start(); 37 Console.Read(); 38 } 39 } 40 41 class TestLock 42 { 43 public static readonly object objLock = new object(); 44 /// <summary> 45 /// 该方法,希望某人在工作的时候,其它人不要打扰(希望只有一个线程在执行) 46 /// </summary> 47 /// <param name="methodIndex"></param> 48 public void DoWorkWithLock() 49 { 50 //锁当前对象 51 lock (this) 52 { 53 Console.WriteLine("lock this"); 54 int i = 0; 55 while (true) 56 { 57 Console.WriteLine("At work, do not disturb...,Thread id is " + Thread.CurrentThread.ManagedThreadId.ToString()); 58 Thread.Sleep(1000); 59 if (i == 5) 60 { 61 break; 62 } 63 Console.WriteLine(i.ToString()); 64 i++; 65 } 66 } 67 Console.WriteLine("lock dispose"); 68 } 69 public void MotherCallYouDinner() 70 { 71 Console.WriteLine("Your mother call you to home for dinner."); 72 } 73 } 74 }
demo说明:main方法中,创建了一个对象testlock对象,线程1执行该对象的DoWorkWithLock方法,因为死锁(5s后释放),造成lock(this)无法释放,则导致了方法MotherCallYouDinner,DoWorkWithLock在线程2中无法被调用,直到lock(this)释放,lock(testlock)才能继续执行,可以这么理解,由于锁定的同一个对象,线程1释放了锁定的对象,其它线程才能访问。
那么通过lock(static object)方式呢,能不能保证lock块内的方法,同时只被一个线程执行呢,并且线程2能访问到MotherCallYouDinner方法。而不像上面出现的那种情况,如果不释放lock(this),导致线程2都无法执行代码逻辑。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading; 6 using System.Threading.Tasks; 7 8 namespace LockTest 9 { 10 class Program 11 { 12 static void Main(string[] args) 13 { 14 TestLock testlock = new TestLock(); 15 Thread th = new Thread(() => 16 { 17 //模拟死锁:造成死锁,使lock无法释放,在i=5时,跳出死循环,释放lock 18 testlock.DoWorkWithLock(); 19 }); 20 th.Start(); 21 Thread.Sleep(1000); 22 Thread th2 = new Thread(() => 23 { 24 25 lock (testlock) 26 { 27 testlock.MotherCallYouDinner(); 28 testlock.DoWorkWithLock(); 29 } 30 }); 31 th2.Start(); 32 Console.Read(); 33 } 34 } 35 36 class TestLock 37 { 38 private static readonly object objLock = new object(); 39 /// <summary> 40 /// 该方法,希望某人在工作的时候,其它人不要打扰(希望只有一个线程在执行) 41 /// </summary> 42 /// <param name="methodIndex"></param> 43 public void DoWorkWithLock() 44 { 45 //锁 46 lock (objLock) 47 { 48 Console.WriteLine("lock this"); 49 int i = 0; 50 while (true) 51 { 52 Console.WriteLine("At work, do not disturb...,Thread id is " + Thread.CurrentThread.ManagedThreadId.ToString()); 53 Thread.Sleep(1000); 54 if (i == 5) 55 { 56 break; 57 } 58 Console.WriteLine(i.ToString()); 59 i++; 60 } 61 } 62 Console.WriteLine("lock dispose"); 63 } 64 public void MotherCallYouDinner() 65 { 66 Console.WriteLine("Your mother call you to home for dinner."); 67 } 68 } 69 }
输出:
可以看到,将lock(this)更换为锁定私有的静态对象,线程2执行了,首先输出了“Your mother call you to home for dinner.”,同时实现了DoWorkWithLock方法中lock的代码块当前只被一个线程执行,直到lcok(objlock)被释放。因为锁定的对象,外部不能访问,线程2不再关心lock(this)是不是已经释放,都会执行,当然也保证了方法DoWorkWithLock同时被一个线程访问。
总结:
1、避免使用lock(this),因为无法保证你提供的方法,在外部类中使用的时候,开发人员会不会锁定当前对象。
通常,应避免锁定 public 类型,否则实例将超出代码的控制范围。常见的结构 lock (this)、lock (typeof (MyType)) 和 lock ("myLock") 违反此准则:
如果实例可以被公共访问,将出现 lock (this) 问题。
如果 MyType 可以被公共访问,将出现 lock (typeof (MyType)) 问题。
由于进程中使用同一字符串的任何其他代码将共享同一个锁,所以出现 lock(“myLock”) 问题。
最佳做法是定义 private 对象来锁定, 或 private static 对象变量来保护所有实例所共有的数据。
这里只是说明lock(this)的问题,虽然极端。但开发中,不能保证不会发生。
2、最好使用私有的静态只读的锁对象,保证不会影响其他逻辑的正常执行。
3、尽量避免死锁的发生。
2.lock自旋锁、互斥锁、混合锁、读写锁
1. 自旋锁:自旋锁(Spinlock)是最简单的线程锁,基于原子操作实现,它使用一个数值来表示锁是否已经被获取,0表示未被获取,1表示已经获取,获取锁时会先使用原子操作设置数值为1,然后检查修改前的值是否为0,如果为0则代表获取成功,否则继续重试直到成功为止,释放锁时会设置数值为0,其他正在获取锁的线程会在下一次重试时成功获取,使用原子操作的原因是,它可以保证多个线程同时把数值0修改到1时,只有一个线程可以观察到修改前的值为0,其他线程观察到修改前的值为1
.NET 可以使用以下的类实现自旋锁:
System.Threading.Thread.SpinWait
System.Threading.SpinWait
System.Threading.SpinLock
使用自旋锁有个需要注意的问题,自旋锁保护的代码应该在非常短的时间内执行完毕,如果代码长时间运行则其他需要获取锁的线程会不断重试并占用逻辑核心,影响其他线程运行,此外,如果 CPU 只有一个逻辑核心,自旋锁在获取失败时应该立刻调用 Thread.Yield 函数提示操作系统切换到其他线程,因为一个逻辑核心同一时间只能运行一个线程,在切换线程之前其他线程没有机会运行,也就是切换线程之前自旋锁没有机会被释放
自旋锁的使用:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 var count = 0; 6 var taskList = new Task[10]; 7 Stopwatch sp = new Stopwatch(); 8 sp.Start(); 9 10 // 不要意外复制。每个实例都是独立的。 11 SpinLock _spinLock = new SpinLock(); 12 for (int i = 0; i < taskList.Length; i++) 13 { 14 taskList[i] = Task.Run(() => 15 { 16 bool _lock = false; 17 for (int j = 0; j < 10_000_000; j++) 18 { 19 _spinLock.Enter(ref _lock); 20 count++; 21 _spinLock.Exit(); 22 _lock = false; 23 } 24 }); 25 } 26 27 sp.Stop(); 28 Task.WaitAll(taskList); 29 Console.WriteLine($"完成! 耗时:{sp.ElapsedTicks}"); 30 Console.WriteLine($"结果:{count}"); 31 } 32 }
自旋锁的简单用法
1 using System; 2 using System.Text; 3 using System.Threading; 4 using System.Threading.Tasks; 5 6 namespace Sample5_4_spinlock 7 { 8 class Program 9 { 10 private static int _TaskNum = 3; 11 private static Task[] _Tasks; 12 private static StringBuilder _StrBlder; 13 private const int RUN_LOOP = 50; 14 15 private static SpinLock m_spinlock; 16 17 private static void Work1(int TaskID) 18 { 19 int i = 0; 20 string log = ""; 21 bool lockToken = false; 22 while (i < RUN_LOOP) 23 { 24 log = String.Format("Time: {0} Task : #{1} Value: {2} =====\n", 25 DateTime.Now.TimeOfDay, TaskID, i); 26 i++; 27 try 28 { 29 lockToken = false; 30 m_spinlock.Enter(ref lockToken); 31 _StrBlder.Append(log); 32 } 33 finally 34 { 35 if (lockToken) 36 m_spinlock.Exit(false); 37 } 38 } 39 } 40 41 private static void Work2(int TaskID) 42 { 43 int i = 0; 44 string log = ""; 45 bool lockToken = false; 46 47 while (i < RUN_LOOP) 48 { 49 log = String.Format("Time: {0} Task : #{1} Value: {2} *****\n", 50 DateTime.Now.TimeOfDay, TaskID, i); 51 i++; 52 try 53 { 54 lockToken = false; 55 m_spinlock.Enter(ref lockToken); 56 _StrBlder.Append(log); 57 } 58 finally 59 { 60 if (lockToken) 61 m_spinlock.Exit(false); 62 } 63 } 64 } 65 66 private static void Work3(int TaskID) 67 { 68 int i = 0; 69 string log = ""; 70 bool lockToken = false; 71 72 while (i < RUN_LOOP) 73 { 74 log = String.Format("Time: {0} Task : #{1} Value: {2} ~~~~~\n", 75 DateTime.Now.TimeOfDay, TaskID, i); 76 i++; 77 try 78 { 79 lockToken = false; 80 m_spinlock.Enter(ref lockToken); 81 _StrBlder.Append(log); 82 } 83 finally 84 { 85 if (lockToken) 86 m_spinlock.Exit(false); 87 } 88 } 89 } 90 91 static void Main(string[] args) 92 { 93 _Tasks = new Task[_TaskNum]; 94 _StrBlder = new StringBuilder(); 95 m_spinlock = new SpinLock(); 96 97 98 _Tasks[0] = Task.Factory.StartNew((num) => 99 { 100 var taskid = (int)num; 101 Work1(taskid); 102 }, 0); 103 104 _Tasks[1] = Task.Factory.StartNew((num) => 105 { 106 var taskid = (int)num; 107 Work2(taskid); 108 }, 1); 109 110 _Tasks[2] = Task.Factory.StartNew((num) => 111 { 112 var taskid = (int)num; 113 Work3(taskid); 114 }, 2); 115 116 var finalTask = Task.Factory.ContinueWhenAll(_Tasks, (tasks) => 117 { 118 Task.WaitAll(_Tasks); 119 Console.WriteLine("=========================================================="); 120 Console.WriteLine("All Phase is completed"); 121 Console.WriteLine("=========================================================="); 122 Console.WriteLine(_StrBlder); 123 }); 124 125 try 126 { 127 finalTask.Wait(); 128 } 129 catch (AggregateException aex) 130 { 131 Console.WriteLine("Task failed And Canceled" + aex.ToString()); 132 } 133 finally 134 { 135 } 136 Console.ReadLine(); 137 } 138 } 139 }
在每个任务的finally块中,会调用SpinLock的release,否则在SpinLock.Enter中,程序会在循环中不断的尝试获得锁,造成死锁。一旦获得了锁,ref 的 LockToken会被变成 true。
超时的用法:SpinLock 同样也提供了超时机制供开发使用。一个程序示例,worker 1会造成超时,work 2 和work 3则在程序中捕获超时产生的异常,终止运行。
1 using System; 2 using System.Text; 3 using System.Threading; 4 using System.Threading.Tasks; 5 6 namespace Sample5_5_spinlock_timeout 7 { 8 class Program 9 { 10 private static int _TaskNum = 3; 11 private static Task[] _Tasks; 12 private static StringBuilder _StrBlder; 13 private const int RUN_LOOP = 50; 14 private static SpinLock m_spinlock; 15 16 17 private static void Work1(int TaskID) 18 { 19 int i = 0; 20 string log = ""; 21 bool lockToken = false; 22 while (i < RUN_LOOP) 23 { 24 log = String.Format("Time: {0} Task : #{1} Value: {2} =====\n", 25 DateTime.Now.TimeOfDay, TaskID, i); 26 i++; 27 try 28 { 29 lockToken = false; 30 m_spinlock.TryEnter(2000, ref lockToken); 31 if (!lockToken) 32 { 33 Console.WriteLine("Work1 TIMEOUT!! Will throw Exception"); 34 throw new TimeoutException("Work1 TIMEOUT!!"); 35 } 36 System.Threading.Thread.Sleep(5000); 37 _StrBlder.Append(log); 38 } 39 finally 40 { 41 if (lockToken) 42 m_spinlock.Exit(false); 43 } 44 } 45 } 46 47 private static void Work2(int TaskID) 48 { 49 int i = 0; 50 string log = ""; 51 bool lockToken = false; 52 53 while (i < RUN_LOOP) 54 { 55 log = String.Format("Time: {0} Task : #{1} Value: {2} *****\n", 56 DateTime.Now.TimeOfDay, TaskID, i); 57 i++; 58 try 59 { 60 lockToken = false; 61 m_spinlock.TryEnter(2000, ref lockToken); 62 if (!lockToken) 63 { 64 Console.WriteLine("Work2 TIMEOUT!! Will throw Exception"); 65 throw new TimeoutException("Work2 TIMEOUT!!"); 66 } 67 68 _StrBlder.Append(log); 69 } 70 finally 71 { 72 if (lockToken) 73 m_spinlock.Exit(false); 74 } 75 } 76 } 77 78 private static void Work3(int TaskID) 79 { 80 int i = 0; 81 string log = ""; 82 bool lockToken = false; 83 84 while (i < RUN_LOOP) 85 { 86 log = String.Format("Time: {0} Task : #{1} Value: {2} ~~~~~\n", 87 DateTime.Now.TimeOfDay, TaskID, i); 88 i++; 89 try 90 { 91 lockToken = false; 92 m_spinlock.TryEnter(2000, ref lockToken); 93 if (!lockToken) 94 { 95 Console.WriteLine("Work3 TIMEOUT!! Will throw Exception"); 96 throw new TimeoutException("Work3 TIMEOUT!!"); 97 } 98 _StrBlder.Append(log); 99 } 100 finally 101 { 102 if (lockToken) 103 m_spinlock.Exit(false); 104 } 105 } 106 } 107 108 static void Main(string[] args) 109 { 110 _Tasks = new Task[_TaskNum]; 111 _StrBlder = new StringBuilder(); 112 m_spinlock = new SpinLock(); 113 114 _Tasks[0] = Task.Factory.StartNew((num) => 115 { 116 var taskid = (int)num; 117 Work1(taskid); 118 }, 0); 119 120 _Tasks[1] = Task.Factory.StartNew((num) => 121 { 122 var taskid = (int)num; 123 Work2(taskid); 124 }, 1); 125 126 _Tasks[2] = Task.Factory.StartNew((num) => 127 { 128 var taskid = (int)num; 129 Work3(taskid); 130 }, 2); 131 132 var finalTask = Task.Factory.ContinueWhenAll(_Tasks, (tasks) => 133 { 134 Task.WaitAll(_Tasks); 135 Console.WriteLine("=========================================================="); 136 Console.WriteLine("All Phase is completed"); 137 Console.WriteLine("=========================================================="); 138 Console.WriteLine(_StrBlder); 139 }); 140 141 try 142 { 143 finalTask.Wait(); 144 } 145 catch (AggregateException aex) 146 { 147 Console.WriteLine("Task failed And Canceled" + aex.ToString()); 148 } 149 finally 150 { 151 } 152 Console.ReadLine(); 153 } 154 155 } 156 }
操作 SpinWait 的另一种方式 – 实例化 SpinWait。
SpinWait 提供了两个方法和两个只读属性。
方法:
SpinWait.Reset() : 重置自旋计数器,将计数器置 0。效果就好像没调用过SpinOnce一样。
SpinWait.Once() : 执行一次自旋。当SpinWait自旋达到一定次数后,如果有必要当前线程会让出底层的时间片并触发上下文切换。
属性:SpinWait.Count:方法执行单次自旋的次数。
SpinWait.NextSpinWillYield:一个bool值,表示下一次通过SpinOnce方法自旋是否会让出底层线程的时间片并发生上下文切换。如果需要等待某个条件满足的时间很短,而且不希望发生上下文切换,基于自旋的【等待】是一种很好的解决方案。
SpinWait : 自旋等待
SpinUntil : 等待某个条件发生
如果发生了长时间的自旋,SpinWait会让出底层的时间片,并触发上下文切换。因为长时间的自旋会阻塞优先级更高的线程。当一个线程自旋时,它会将一个内核放入到一个繁忙的循环中,而且它不会让出处理器时间片的剩余部分。SpinWait的智能逻辑中会在自旋达到足够长的时间时停止自旋并让出处理器。当然可以考虑调用Thread.Sleep()方法,它会让出处理器时间,但开销比较大。
示例程序:这里通过使用SpinWait 来控制3个Task的执行顺序。
1 using System; 2 using System.Text; 3 using System.Threading; 4 using System.Threading.Tasks; 5 6 namespace Sample5_6_spinwait 7 { 8 class Program 9 { 10 private static int _TaskNum = 3; 11 private static Task[] _Tasks; 12 private static StringBuilder _StrBlder; 13 private const int RUN_LOOP = 10; 14 private static bool m_IsWork2Start = false; 15 private static bool m_IsWork3Start = false; 16 17 private static void Work1(int TaskID) 18 { 19 int i = 0; 20 string log = ""; 21 22 while (i < RUN_LOOP) 23 { 24 log = String.Format("Time: {0} Task : #{1} Value: {2} =====\n", 25 DateTime.Now.TimeOfDay, TaskID, i); 26 i++; 27 try 28 { 29 _StrBlder.Append(log); 30 } 31 finally 32 { 33 m_IsWork2Start = true; 34 } 35 } 36 } 37 38 private static void Work2(int TaskID) 39 { 40 int i = 0; 41 string log = ""; 42 43 System.Threading.SpinWait.SpinUntil(() => m_IsWork2Start); 44 45 while ((i < RUN_LOOP) && (m_IsWork2Start)) 46 { 47 log = String.Format("Time: {0} Task : #{1} Value: {2} *****\n", 48 DateTime.Now.TimeOfDay, TaskID, i); 49 i++; 50 try 51 { 52 _StrBlder.Append(log); 53 } 54 finally 55 { 56 m_IsWork3Start = true; 57 } 58 } 59 } 60 61 private static void Work3(int TaskID) 62 { 63 int i = 0; 64 string log = ""; 65 66 System.Threading.SpinWait.SpinUntil(() => m_IsWork3Start); 67 68 while (i < RUN_LOOP) 69 { 70 log = String.Format("Time: {0} Task : #{1} Value: {2} ~~~~~\n", 71 DateTime.Now.TimeOfDay, TaskID, i); 72 i++; 73 try 74 { 75 _StrBlder.Append(log); 76 } 77 finally 78 { 79 } 80 } 81 } 82 83 static void Main(string[] args) 84 { 85 _Tasks = new Task[_TaskNum]; 86 _StrBlder = new StringBuilder(); 87 88 _Tasks[0] = Task.Factory.StartNew((num) => 89 { 90 var taskid = (int)num; 91 Work1(taskid); 92 }, 0); 93 94 _Tasks[1] = Task.Factory.StartNew((num) => 95 { 96 var taskid = (int)num; 97 Work2(taskid); 98 }, 1); 99 100 _Tasks[2] = Task.Factory.StartNew((num) => 101 { 102 var taskid = (int)num; 103 Work3(taskid); 104 }, 2); 105 106 var finalTask = Task.Factory.ContinueWhenAll(_Tasks, (tasks) => 107 { 108 Task.WaitAll(_Tasks); 109 Console.WriteLine("=========================================================="); 110 Console.WriteLine("All Phase is completed"); 111 Console.WriteLine("=========================================================="); 112 Console.WriteLine(_StrBlder); 113 }); 114 115 try 116 { 117 finalTask.Wait(); 118 } 119 catch (AggregateException aex) 120 { 121 Console.WriteLine("Task failed And Canceled" + aex.ToString()); 122 } 123 finally 124 { 125 } 126 Console.ReadLine(); 127 } 128 } 129 }
总结:
自旋锁可用于叶级锁, 在这种情况Monitor下, 通过使用、大小或由于垃圾回收压力而隐含的对象分配的成本非常高。 旋转锁定有助于避免阻塞;但是, 如果你预计会有大量的阻塞, 则可能由于旋转过多而无法使用自旋锁。 当锁的粒度较大且数值较大 (例如, 链接列表中的每个节点都有一个锁) 以及锁保留时间始终极短时, 旋转可能非常有利。 通常, 在持有自旋锁时, 应避免使用以下任何操作:
堵塞
调用自身可能会阻止的任何内容,
同时保留多个自旋锁,
进行动态调度的调用 (interface 和虚方法),
对任何代码进行静态调度调用, 而不是任何代码, 或
分配内存。
SpinLock仅应在确定这样做后使用才能改善应用程序的性能。 出于性能方面的考虑, 还SpinLock必须注意, 是值类型。 出于此原因, 必须注意不要意外复制SpinLock实例, 因为两个实例 (原始和副本) 将完全独立, 这可能会导致应用程序出现错误的行为。 如果必须传递实例, 则它应按引用而不是按值传递。 SpinLock
不要在只读SpinLock字段中存储实例。
2. 互斥锁:Monitor 和 mutex
定义:private static readonly object Lock = new object();
使用:Monitor.Enter(Lock); //todo Monitor.Exit(Lock);
作用:将会锁住代码块的内容,并阻止其他线程进入该代码块,直到该代码块运行完成,释放该锁。
注意:定义的锁对象应该是 私有的,静态的,只读的,引用类型的对象,这样可以防止外部改变锁对象
Monitor有TryEnter的功能,可以防止出现死锁的问题,lock没有
定义:private static readonly Mutex mutex = new Mutex();
使用:mutex.WaitOne(); //todo mutex.ReleaseMutex();
作用:将会锁住代码块的内容,并阻止其他线程进入该代码块,直到该代码块运行完成,释放该锁。
注意:定义的锁对象应该是 私有的,静态的,只读的,引用类型的对象,这样可以防止外部改变锁对象
Mutex本身是可以系统级别的,所以是可以跨越进程的
Monitor 测试实现:
1 using System; 2 using System.Text; 3 using System.Threading; 4 using System.Threading.Tasks; 5 6 namespace Sample5_2_monitor_lock 7 { 8 class Program 9 { 10 private static int _TaskNum = 3; 11 private static Task[] _Tasks; 12 private static StringBuilder _StrBlder; 13 private const int RUN_LOOP = 50; 14 15 private static void Work1(int TaskID) 16 { 17 int i = 0; 18 string log = ""; 19 bool lockToken = false; 20 while (i < RUN_LOOP) 21 { 22 log = String.Format("Time: {0} Task : #{1} Value: {2} =====\n", 23 DateTime.Now.TimeOfDay, TaskID, i); 24 i++; 25 try 26 { 27 lockToken = false; 28 Monitor.Enter(_StrBlder, ref lockToken); 29 _StrBlder.Append(log); 30 } 31 finally 32 { 33 if (lockToken) 34 Monitor.Exit(_StrBlder); 35 } 36 } 37 } 38 39 private static void Work2(int TaskID) 40 { 41 int i = 0; 42 string log = ""; 43 bool lockToken = false; 44 45 while (i < RUN_LOOP) 46 { 47 log = String.Format("Time: {0} Task : #{1} Value: {2} *****\n", 48 DateTime.Now.TimeOfDay, TaskID, i); 49 i++; 50 try 51 { 52 lockToken = false; 53 Monitor.Enter(_StrBlder, ref lockToken); 54 _StrBlder.Append(log); 55 } 56 finally 57 { 58 if (lockToken) 59 Monitor.Exit(_StrBlder); 60 } 61 } 62 } 63 64 private static void Work3(int TaskID) 65 { 66 int i = 0; 67 string log = ""; 68 bool lockToken = false; 69 70 while (i < RUN_LOOP) 71 { 72 log = String.Format("Time: {0} Task : #{1} Value: {2} ~~~~~\n", 73 DateTime.Now.TimeOfDay, TaskID, i); 74 i++; 75 try 76 { 77 lockToken = false; 78 Monitor.Enter(_StrBlder, ref lockToken); 79 _StrBlder.Append(log); 80 } 81 finally 82 { 83 if (lockToken) 84 Monitor.Exit(_StrBlder); 85 } 86 } 87 } 88 89 static void Main(string[] args) 90 { 91 _Tasks = new Task[_TaskNum]; 92 _StrBlder = new StringBuilder(); 93 94 _Tasks[0] = Task.Factory.StartNew((num) => 95 { 96 var taskid = (int)num; 97 Work1(taskid); 98 }, 0); 99 100 _Tasks[1] = Task.Factory.StartNew((num) => 101 { 102 var taskid = (int)num; 103 Work2(taskid); 104 }, 1); 105 106 _Tasks[2] = Task.Factory.StartNew((num) => 107 { 108 var taskid = (int)num; 109 Work3(taskid); 110 }, 2); 111 112 var finalTask = Task.Factory.ContinueWhenAll(_Tasks, (tasks) => 113 { 114 Task.WaitAll(_Tasks); 115 Console.WriteLine("=========================================================="); 116 Console.WriteLine("All Phase is completed"); 117 Console.WriteLine("=========================================================="); 118 Console.WriteLine(_StrBlder); 119 }); 120 121 try 122 { 123 finalTask.Wait(); 124 } 125 catch (AggregateException aex) 126 { 127 Console.WriteLine("Task failed And Canceled" + aex.ToString()); 128 } 129 finally 130 { 131 } 132 Console.ReadLine(); 133 } 134 } 135 }
锁超时的使用:
其中主要使用的是 Monitor.TryEnter(),函数,其中多了一个设置超时时间的参数。
代码中让每个锁的超时Timer为2秒,在Work1中挺顿5秒,这样造成了Work2和Work3的超时。
1 using System; 2 using System.Text; 3 using System.Threading; 4 using System.Threading.Tasks; 5 6 namespace Sample5_3_monitor_lock_timeout 7 { 8 class Program 9 { 10 private static int _TaskNum = 3; 11 private static Task[] _Tasks; 12 private static StringBuilder _StrBlder; 13 private const int RUN_LOOP = 50; 14 15 private static void Work1(int TaskID) 16 { 17 int i = 0; 18 string log = ""; 19 bool lockToken = false; 20 while (i < RUN_LOOP) 21 { 22 log = String.Format("Time: {0} Task : #{1} Value: {2} =====\n", 23 DateTime.Now.TimeOfDay, TaskID, i); 24 i++; 25 try 26 { 27 lockToken = false; 28 Monitor.TryEnter(_StrBlder, 2000, ref lockToken); 29 if (!lockToken) 30 { 31 Console.WriteLine("Work1 TIMEOUT!! Will throw Exception"); 32 throw new TimeoutException("Work1 TIMEOUT!!"); 33 } 34 System.Threading.Thread.Sleep(5000); 35 _StrBlder.Append(log); 36 } 37 finally 38 { 39 if (lockToken) 40 Monitor.Exit(_StrBlder); 41 } 42 } 43 } 44 45 private static void Work2(int TaskID) 46 { 47 int i = 0; 48 string log = ""; 49 bool lockToken = false; 50 51 while (i < RUN_LOOP) 52 { 53 log = String.Format("Time: {0} Task : #{1} Value: {2} *****\n", 54 DateTime.Now.TimeOfDay, TaskID, i); 55 i++; 56 try 57 { 58 lockToken = false; 59 Monitor.TryEnter(_StrBlder, 2000, ref lockToken); 60 if (!lockToken) 61 { 62 Console.WriteLine("Work2 TIMEOUT!! Will throw Exception"); 63 throw new TimeoutException("Work2 TIMEOUT!!"); 64 } 65 66 _StrBlder.Append(log); 67 } 68 finally 69 { 70 if (lockToken) 71 Monitor.Exit(_StrBlder); 72 } 73 } 74 } 75 76 private static void Work3(int TaskID) 77 { 78 int i = 0; 79 string log = ""; 80 bool lockToken = false; 81 82 while (i < RUN_LOOP) 83 { 84 log = String.Format("Time: {0} Task : #{1} Value: {2} ~~~~~\n", 85 DateTime.Now.TimeOfDay, TaskID, i); 86 i++; 87 try 88 { 89 lockToken = false; 90 Monitor.TryEnter(_StrBlder, 2000, ref lockToken); 91 if (!lockToken) 92 { 93 Console.WriteLine("Work3 TIMEOUT!! Will throw Exception"); 94 throw new TimeoutException("Work3 TIMEOUT!!"); 95 } 96 _StrBlder.Append(log); 97 } 98 finally 99 { 100 if (lockToken) 101 Monitor.Exit(_StrBlder); 102 } 103 } 104 } 105 106 static void Main(string[] args) 107 { 108 _Tasks = new Task[_TaskNum]; 109 _StrBlder = new StringBuilder(); 110 111 _Tasks[0] = Task.Factory.StartNew((num) => 112 { 113 var taskid = (int)num; 114 Work1(taskid); 115 }, 0); 116 117 _Tasks[1] = Task.Factory.StartNew((num) => 118 { 119 var taskid = (int)num; 120 Work2(taskid); 121 }, 1); 122 123 _Tasks[2] = Task.Factory.StartNew((num) => 124 { 125 var taskid = (int)num; 126 Work3(taskid); 127 }, 2); 128 129 var finalTask = Task.Factory.ContinueWhenAll(_Tasks, (tasks) => 130 { 131 Task.WaitAll(_Tasks); 132 Console.WriteLine("=========================================================="); 133 Console.WriteLine("All Phase is completed"); 134 Console.WriteLine("=========================================================="); 135 Console.WriteLine(_StrBlder); 136 }); 137 138 try 139 { 140 finalTask.Wait(); 141 } 142 catch (AggregateException aex) 143 { 144 Console.WriteLine("Task failed And Canceled" + aex.ToString()); 145 } 146 finally 147 { 148 } 149 Console.ReadLine(); 150 } 151 } 152 }
mutex 测试实现 :
1、initiallyOwned表示创建mutex的线程是否拥有该互斥体。true表示创建线程拥有互斥锁,只有在创建线程中调用ReleaseMutex释放后,其他等待线程才能参与抢夺互斥体的活动。false表示互斥锁体于与空闲状态,其他等待互斥锁的线程立即参与到抢夺互斥锁的活动中去。
2、在上面程序中如果创建mutex时使用true参数,故在启动其他线程后必须执行mutex.ReleaseMutex(),如果不释放mutex,则其他线程将一直等待下去。使用ture,相当于一创建就使用waitone()
3、mutex.WaitOne()与mutex.ReleaseMutex()要像 { } 一样配对使用,否则将出现 "由于出现被放弃的 mutex,等待过程结束" 的异常
4、mutex与monitor相比,没有暂时释放的功能;因此mutex一经释放,原释放资源的线程也将重新参与新一轮对mutex的争夺过程。
1 using (var mutex = new Mutex(false, "name")) 2 { 3 try 4 { 5 mutex.WaitOne(); 6 //do something 7 } 8 catch(Exception ex) 9 { 10 throw ex; 11 } 12 finally 13 { 14 mutex.ReleaseMutex(); 15 } 16 }
不举栗子了:https://www.cnblogs.com/leo_wl/archive/2012/03/23/2413200.html,,,,https://www.cnblogs.com/guozhiming2003/archive/2008/09/16/1291953.html
3. 混合锁
混合锁的特征是在获取锁失败后像自旋锁一样重试一定的次数,超过一定次数之后(.NET Core 2.1 是30次)再安排当前进程进入等待状态
混合锁的好处是,如果第一次获取锁失败,但其他线程马上释放了锁,当前线程在下一轮重试可以获取成功,不需要执行毫秒级的线程调度处理;而如果其他线程在短时间内没有释放锁,线程会在超过重试次数之后进入等待状态,以避免消耗 CPU 资源,因此混合锁适用于大部分场景
1 internal sealed class SimpleHybridLock : IDisposable 2 { 3 //基元用户模式构造使用 4 private int m_waiters = 0; 5 6 //基元内核模式构造 7 private AutoResetEvent m_waiterLock = new AutoResetEvent(false); 8 9 public void Enter() 10 { 11 //指出该线程想要获得锁 12 if (Equals(Interlocked.Increment(ref m_waiters), 1)) 13 { 14 //无竞争,直接返回 15 return; 16 } 17 18 //另一个线程拥有锁(发生竞争),使这个线程等待 19 //线程会阻塞,但不会在CPU上“自旋”,从而节省CPU 20 //这里产生较大的性能影响(用户模式与内核模式之间转换) 21 //待WaitOne返回后,这个线程拿到锁 22 m_waiterLock.WaitOne(); 23 } 24 25 public void Leave() 26 { 27 //该线程准备释放锁 28 if (Equals(Interlocked.Decrement(ref m_waiters), 0)) 29 { 30 //无线程等待,直接返回 31 return; 32 } 33 34 //有线程等待则唤醒其中一个 35 //这里产生较大的性能影响(用户模式与内核模式之间转换) 36 m_waiterLock.Set(); 37 } 38 39 public void Dispose() 40 { 41 m_waiterLock.Dispose(); 42 } 43 }
4.读写锁(ReaderWriterLock )
ReaderWriterLock 定义支持单个写线程和多个读线程的锁。该锁的作用主要是解决并发读的性能问题,使用该锁,可以大大提高数据并发访问的性能,只有在写时,才会阻塞所有的读锁。
1 using System.Collections.Generic; 2 using System.Windows; 3 using System.Threading; 4 5 6 namespace FYSTest 7 { 8 public partial class MainWindow : Window 9 { 10 List<int> list = new List<int>(); 11 private ReaderWriterLock _rwlock = new ReaderWriterLock(); 12 13 public MainWindow() 14 { 15 InitializeComponent(); 16 Thread ThRead = new Thread(new ThreadStart(Read)); 17 ThRead.IsBackground = true; 18 Thread ThRead2 = new Thread(new ThreadStart(Read)); 19 ThRead2.IsBackground = true; 20 Thread ThWrite = new Thread(new ThreadStart(Write)); 21 ThWrite.IsBackground = true; 22 ThRead.Start(); 23 ThRead2.Start(); 24 ThWrite.Start(); 25 } 26 27 private void Read() 28 { 29 while (true) 30 { 31 //使用一个 System.Int32 超时值获取读线程锁。 32 _rwlock.AcquireReaderLock(100); 33 try 34 { 35 if (list.Count > 0) 36 { 37 int result = list[list.Count - 1]; 38 } 39 } 40 finally 41 { 42 //减少锁计数,释放锁 43 _rwlock.ReleaseReaderLock(); 44 } 45 } 46 } 47 48 int WriteCount = 0;//写次数 49 private void Write() 50 { 51 while (true) 52 { 53 //使用一个 System.Int32 超时值获取写线程锁。 54 _rwlock.AcquireWriterLock(100); 55 try 56 { 57 list.Add(WriteCount++); 58 } 59 finally 60 { 61 //减少写线程锁上的锁计数,释放写锁 62 _rwlock.ReleaseWriterLock(); 63 } 64 } 65 } 66 } 67 }
读写锁的具体用法栗子:https://blog.csdn.net/zdhlwt2008/article/details/80702605