谈谈 Lock
上来先看MSDN关于lock的叙述:
lock 关键字将语句块标记为临界区,方法是获取给定对象的互斥锁,执行语句,然后释放该锁。 下面的示例包含一个 lock 语句。
lock 关键字可确保当一个线程位于代码的临界区时,另一个线程不会进入该临界区。 如果其他线程尝试进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。
线程处理(C# 和 Visual Basic) 这节讨论了线程处理。
lock 关键字在块的开始处调用 Enter,而在块的结尾处调用 Exit。 ThreadInterruptedException 引发,如果 Interrupt 中断等待输入 lock 语句的线程。
通常,应避免锁定 public 类型,否则实例将超出代码的控制范围。 常见的结构 lock (this)、lock (typeof (MyType)) 和 lock ("myLock") 违反此准则:
• 如果实例可以被公共访问,将出现 lock (this) 问题。
• 如果 MyType 可以被公共访问,将出现 lock (typeof (MyType)) 问题。
• 由于进程中使用同一字符串的任何其他代码都将共享同一个锁,所以出现 lock("myLock") 问题。
最佳做法是定义 private 对象来锁定, 或 private static 对象变量来保护所有实例所共有的数据。
在 lock 语句的正文不能使用 等待 关键字。
通读上面叙述后,再来几个事例看看:
例一:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading; 6 7 namespace NB.Demo 8 { 9 class Program 10 { 11 static void Main(string[] args) 12 { 13 Test t = new Test(); 14 Thread[] threads = new Thread[10]; 15 for (int i = 0; i < threads.Length; i++) 16 { 17 18 threads[i] = new Thread(() => 19 { 20 t.Print(); 21 }); 22 23 threads[i].Name = "thread" + i; 24 25 } 26 27 for (int i = 0; i < threads.Length; i++) 28 { 29 threads[i].Start(); 30 } 31 32 Console.Read(); 33 } 34 } 35 class MyLock 36 { 37 public int Id { get; set; } 38 } 39 40 class Test 41 { 42 public void Print() 43 { 44 MyLock myLock = new MyLock(); 45 lock (myLock) 46 { 47 for (int i = 0; i < 10; i++) 48 { 49 Console.WriteLine("\t" + Thread.CurrentThread.Name.ToString() + "\t" + i.ToString() + " "); 50 } 51 } 52 } 53 } 54 }
输出结果: 线程出现了争抢,这不是我们想要的结果,我们预期是每次只有一个线程去执行Print方法。
例二:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading; 6 7 namespace NB.Demo 8 { 9 class Program 10 { 11 static void Main(string[] args) 12 { 13 Test t = new Test(); 14 Test t0 = new Test(); 15 Thread[] threads = new Thread[10]; 16 for (int i = 0; i < threads.Length; i++) 17 { 18 19 threads[i] = new Thread(() => 20 { 21 t.Print(); 22 t0.Print(); 23 }); 24 25 threads[i].Name = "thread" + i; 26 27 } 28 29 for (int i = 0; i < threads.Length; i++) 30 { 31 threads[i].Start(); 32 } 33 34 Console.Read(); 35 } 36 } 37 class MyLock 38 { 39 public int Id { get; set; } 40 } 41 42 class Test 43 { 44 public void Print() 45 { 46 lock (this) 47 { 48 49 for (int i = 0; i < 10; i++) 50 { 51 Console.WriteLine("\t" + Thread.CurrentThread.Name.ToString() + "\t" + i.ToString() + " "); 52 } 53 } 54 } 55 } 56 }
输出结果: 同样线程出现了争抢,这不是我们想要的结果。
例三:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading; 6 7 namespace NB.Demo 8 { 9 class Program 10 { 11 static void Main(string[] args) 12 { 13 Test t = new Test(); 14 15 Thread[] threads = new Thread[10]; 16 for (int i = 0; i < threads.Length; i++) 17 { 18 19 threads[i] = new Thread(() => 20 { 21 t.Print(); 22 }); 23 24 threads[i].Name = "thread" + i; 25 26 } 27 28 for (int i = 0; i < threads.Length; i++) 29 { 30 threads[i].Start(); 31 } 32 33 Console.Read(); 34 } 35 } 36 class MyLock 37 { 38 public int Id { get; set; } 39 } 40 41 class Test 42 { 43 MyLock myobj = new MyLock(); 44 public void Print() 45 { 46 lock (myobj) 47 { 48 49 for (int i = 0; i < 10; i++) 50 { 51 Console.WriteLine("\t" + Thread.CurrentThread.Name.ToString() + "\t" + i.ToString() + " "); 52 } 53 } 54 } 55 } 56 }
例四:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading; 6 7 namespace NB.Demo 8 { 9 class Program 10 { 11 static void Main(string[] args) 12 { 13 Test t = new Test(); 14 15 Thread[] threads = new Thread[10]; 16 for (int i = 0; i < threads.Length; i++) 17 { 18 19 threads[i] = new Thread(() => 20 { 21 t.Print(); 22 }); 23 24 threads[i].Name = "thread" + i; 25 26 } 27 28 for (int i = 0; i < threads.Length; i++) 29 { 30 threads[i].Start(); 31 } 32 33 Console.Read(); 34 } 35 } 36 class MyLock 37 { 38 public int Id { get; set; } 39 } 40 41 class Test 42 { 43 private static object obj = new object(); 44 public void Print() 45 { 46 lock (obj) 47 { 48 49 for (int i = 0; i < 10; i++) 50 { 51 Console.WriteLine("\t" + Thread.CurrentThread.Name.ToString() + "\t" + i.ToString() + " "); 52 } 53 } 54 } 55 } 56 }
例五:其他情况 如 Lock(type), Lock("string"),Lock("this")等
总结:
引用类型对象除实例字段的开销外,还有两个字段的开销:类型指针和同步块索引(SyncBlockIndex)。 在.net CLR世界,lock实际上是Moniter的包装。CLR初始化的时候,CLR会初始化一个SyncBlock的数组,当一个线程到达Monitor.Enter方法时,该线程会检查该方法接受的参数的同步块索引,默认情况下对象的同步块索引是一个默认值,那么表明该对象并没有一个关联的同步块,CLR就会在全局的SyncBlock数组里找到一个空闲的项,然后将数组的索引赋值给该对象的同步块索引,SyncBlock的内容和CRITICAL_SECTION的内容很相似,当Monitor.Enter执行时,它会设置SyncBlock里的内容,标识出已经有一个线程占用了,当另外一个线程进入时,它就会检查SyncBlock的内容,发现已经有一个线程占用了,该线程就会等待,当Monitor.Exit执行时,占用的线程就会释放SyncBlock,其他的线程可以进入操作了。
针对例一,锁定的对象是作为一个局部变量,每个线程进入的时候,锁定的对象都会不一样,它的SyncBlock每一次都是重新分配的,这个根本谈不上什么锁定不锁定。
针对例二,锁定的对象是Test类事例this,Test t和Test t0所代表的this都会不一样,它的SyncBlock就不一样,所以这种Lock也是无效的。
针对例五的那些其他情况:
一般说来应该没有什么事情,但这些操作却是很危险的。
typeof(..)得到的是..类的Type对象,所有..实例的Type都是同一个,Type对象也是一个对象,它也有自己的SyncBlock,Singleton的Type对象的SyncBlock在程序中只会有一份,为什么说这种做法是危险的呢?如果在该程序中,其他毫不相干的地方我们也使用了lock(typeof(..)),虽然它和这里的锁定毫无关系,但是只要一个地方锁定了,各个地方的线程都会在等待。
string也是应用类型,从语法上来说是没有错的。但是锁定字符串尤其危险,因为字符串被公共语言运行库 (CLR)“暂留”。 这意味着整个程序中任何给定字符串都只有一个实例,就是这同一个对象表示了所有运行的应用程序域的所有线程中的该文本。因此,只要在应用程序进程中的任何位置处具有相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的所有实例。
针对例三,本试验是没问题,但这种方式是不是有点不好的地方,留给各位思考。