本节书摘来自华章出版社《C#多线程编程实战(原书第2版)》一书中的第1章,第1.10节,作者(美)易格恩·阿格佛温(Eugene Agafonov),黄博文 黄辉兰 译,更多章节内容可以访问云栖社区“华章计算机”公众号查看。
1.10 使用C#中的lock关键字
本节将描述如何确保当一个线程使用某些资源时,同时其他线程无法使用该资源。我们将了解该情况的必要性及整个线程安全概念都包含什么。
1.10.1 准备工作
为了学习本节,你需要安装Visual Studio 2015。除此之外无需其他准备。本节的源代码放置在BookSamplesChapter1Recipe9目录中。
1.10.2 实现方式
请执行以下步骤来了解如何使用C#中的lock关键字:
1.启动Visual Studio 2015。新建一个C#控制台应用程序项目。
2.在Program.cs文件中加入以下using指令:
3.在Main方法下面加入以下代码片段:
4.在Main方法中加入以下代码片段:
5.运行程序。
1.10.3 工作原理
当主程序启动时,创建了一个Counter类的对象。该类定义了一个可以递增和递减的简单的计数器。然后我们启动了三个线程。这三个线程共享同一个counter实例,在一个周期中进行一次递增和一次递减。这将导致不确定的结果。如果运行程序多次,则会打印出多个不同的计数器值。结果可能是0,但大多数情况下则不是0。
这是因为Counter类并不是线程安全的。当多个线程同时访问counter对象时,第一个线程得到的counter值10并增加为11。然后第二个线程得到的值是11并增加为12。第一个线程得到counter值12,但是递减操作发生前,第二个线程得到的counter值也是12。然后第一个线程将12递减为11并保存回counter中,同时第二个线程进行了同样的操作。结果我们进行了两次递增操作但是只有一次递减操作,这显然不对。这种情形被称为竞争条件(race condition)。竞争条件是多线程环境中非常常见的导致错误的原因。
为了确保不会发生以上情形,必须保证当有线程操作counter对象时,所有其他线程必须等待直到当前线程完成操作。我们可以使用lock关键字来实现这种行为。如果锁定了一个对象,需要访问该对象的所有其他线程则会处于阻塞状态,并等待直到该对象解除锁定。这可能会导致严重的性能问题,在第2章中将会进一步学习该知识点。