有的时候,系统需要子线程拥有新的上下文。抛开功能上的需求,执行上下文的流动确实使得程序的执行效率下降很多,线程上下文的包装是一个成本较高的工作,而有的时候这样的包装并不是必须的。在这种情况下,我们如果需要手动地防止线程上下文的流动,常用的有下列两种方法:
① System.Threading.ThreadPool类中的UnsafeQueueUserWorkItem方法
② ExecutionContext类中的SuppressFlow方法
下面的代码示例展示了如何使用上面两种方法阻止执行上下文的流动:
partial class Program { testFile = ; static void Main(string[] args) { try { CreateTestFile(); // 现在修改安全上下文,阻止文件访问 FileIOPermission fip = new FileIOPermission(FileIOPermissionAccess.AllAccess, testFile); fip.Deny(); Console.WriteLine(); // 主线程权限测试 Console.WriteLine(); JudgePermission(null); // 使用UnsafeQueueUserWorkItem方法创建一个子线程 Console.WriteLine(); ThreadPool.UnsafeQueueUserWorkItem(JudgePermission, null); Thread.Sleep(1000); (var afc = ExecutionContext.SuppressFlow()) { // 测试当前线程安全上下文 Console.WriteLine(); JudgePermission(null); // 创建一个子线程 subThread1 Console.WriteLine(); Thread subThread1 = new Thread(JudgePermission); subThread1.Start(); subThread1.Join(); } // 现在修改安全上下文,允许文件访问 SecurityPermission.RevertDeny(); Console.WriteLine(); // 测试当前线程安全上下文 Console.WriteLine(); JudgePermission(null); // 创建一个子线程 subThread2 Console.WriteLine(); Thread subThread2 = new Thread(JudgePermission); subThread2.Start(); subThread2.Join(); Console.ReadKey(); } finally { DeleteTestFile(); } } }
View Code该实例的执行结果如下图所示,可以看出,通过前面的两种方式有效地阻止了主线程的执行上下文流动到新建的线程之中,这样的机制对于性能的提高有一定的帮助。
同步块是.NET中解决对象同步问题的基本机制,该机制为每个堆内的对象(即引用类型对象实例)分配一个同步索引,该索引中只保存一个表明数组内索引的整数。具体过程是:.NET在加载时就会新建一个同步块数组,当某个对象需要被同步时,.NET会为其分配一个同步块,并且把该同步块在同步块数组中的索引加入该对象的同步块索引中。下图展现了这一机制的实现:
同步块机制包含以下几点:
① 在.NET被加载时初始化同步块数组;
② 每一个被分配在堆上的对象都会包含两个额外的字段,其中一个存储类型指针,而另外一个就是同步块索引,初始时被赋值为-1;
③ 当一个线程试图使用该对象进入同步时,会检查该对象的同步索引:
如果同步索引为负数,则会在同步块数组中新建一个同步块,并且将该同步块的索引值写入该对象的同步索引中;
如果同步索引不为负数,则找到该对象的同步块并检查是否有其他线程在使用该同步块,如果有则进入等待状态,如果没有则申明使用该同步块;
④ 当一个对象退出同步时,该对象的同步索引被修改为-1,并且相应的同步块数组中的同步块被视为不再使用。
3.2 C#中的lock关键字有啥作用?lock关键字可能是我们在遇到线程同步的需求时最常用的方式,但lock只是一个语法糖,为什么这么说呢,下面慢慢道来。
(1)lock的等效代码其实是Monitor类的Enter和Exit两个方法