C#多线程系列之多线程锁lock和Monitor

C#多线程系列之多线程锁lock和Monitor

目录

1,Lock

lock 原型

lock 编写实例

2,Monitor

怎么用呢

解释一下

示例

设置获取锁的时效

1,Lock

lock 用于读一个引用类型进行加锁,同一时刻内只有一个线程能够访问此对象。lock 是语法糖,是通过 Monitor 来实现的。

Lock 锁定的对象,应该是静态的引用类型(字符串除外)。

实际上字符串也可以作为锁的对象使用,只是由于字符串对象的特殊性,可能会造成不同位置的不同线程冲突。
如果你能保证字符串的唯一性,例如 Guid 生成的字符串,也是可以作为锁的对象使用的(但不建议)。 
锁的对象也不一定要静态才行,也可以通过类实例的成员变量,作为锁对象。

lock 原型

lock 是 Monitor 的语法糖,生成的代码对比:

lock (x) { // Your code... } object __lockObj = x; bool __lockWasTaken = false; try { System.Threading.Monitor.Enter(__lockObj, ref __lockWasTaken); // Your code... } finally { if (__lockWasTaken) System.Threading.Monitor.Exit(__lockObj); }

这里先不理会 Monitor,后面再说。

lock 编写实例

首先,如果像下面这样写的话,拉出去打 si 吧。

public void MyLock() { object o = new object(); lock (o) { // } }

下面编写一个简单的锁,示例如下:

class Program { private static object obj = new object(); private static int sum = 0; static void Main(string[] args) { Thread thread1 = new Thread(Sum1); thread1.Start(); Thread thread2 = new Thread(Sum2); thread2.Start(); while (true) { Console.WriteLine($"{DateTime.Now.ToString()}:" + sum); Thread.Sleep(TimeSpan.FromSeconds(1)); } } public static void Sum1() { sum = 0; lock (obj) { for (int i = 0; i < 10; i++) { sum += i; Console.WriteLine("Sum1"); Thread.Sleep(TimeSpan.FromSeconds(2)); } } } public static void Sum2() { sum = 0; lock (obj) { for (int i = 0; i < 10; i++) { sum += 1; Console.WriteLine("Sum2"); Thread.Sleep(TimeSpan.FromSeconds(2)); } } } }

类将自己设置为锁, 这可以防止恶意代码对公共对象采用做锁。

例如:

public void Access() { lock(this) {} }

锁可以阻止其它线程执行锁块(lock(o){})中的代码,当锁定时,其它线程必须等待锁中的线程执行完成并释放锁。但是这可能会给程序带来性能影响。
锁不太适合I/O场景,例如文件I/O,繁杂的计算或者操作比较持久的过程,会给程序带来很大的性能损失。

10 种优化锁的性能方法: http://www.thinkingparallel.com/2007/07/31/10-ways-to-reduce-lock-contention-in-threaded-programs/

2,Monitor

此对象提供同步访问对象的机制;Monotor 是一个静态类型,其方法比较少,常用方法如下:

操作说明
Enter, TryEnter获取对象的锁。 此操作还标记关键节的开头。 其他任何线程都不能输入临界区,除非它使用不同的锁定对象执行临界区中的说明。
Wait释放对象的锁,以允许其他线程锁定并访问对象。 调用线程会等待另一个线程访问对象。 使用脉冲信号通知等待线程关于对象状态的更改。
Pulse 、PulseAll将信号发送到一个或多个等待线程。 信号通知等待线程:锁定对象的状态已更改,锁的所有者已准备好释放该锁。 正在等待的线程置于对象的就绪队列中,因此它可能最终接收对象的锁。 线程锁定后,它可以检查对象的新状态,以查看是否已达到所需的状态。
Exit释放对象的锁。 此操作还标记受锁定对象保护的临界区的结尾。
怎么用呢

下面是一个很简单的示例:

private static object obj = new object(); private static bool acquiredLock = false; public static void Test() { try { Monitor.Enter(obj, ref acquiredLock); } catch { } finally { if (acquiredLock) Monitor.Exit(obj); } }

Monitor.Enter 锁定 obj 这个对象,并且设置 acquiredLock 为 true,告诉别人 obj 已经被锁定。

最后结束时,判断 acquiredLock ,释放锁,并设置 acquiredLock 为 false。

解释一下

临界区:指被某些符号包围的范围。例如 {} 内。

Monitor 对象的 Enter 和 Exit 方法来标记临界区的开头和结尾。

Enter() 方法获取锁后,能够保证只有单个线程能够使用临界区中的代码。使用 Monitor 类,最好搭配 try{...}catch{...}finally{...} 来使用,因为如果获取到锁但是没有释放锁的话,会导致其它线程无限阻塞,即发生死锁。

一般来说,lock 关键字够用了。

示例

下面示范了多个线程如何使用 Monitor 来实现锁:

private static object obj = new object(); private static bool acquiredLock = false; static void Main(string[] args) { new Thread(Test1).Start(); Thread.Sleep(1000); new Thread(Test2).Start(); } public static void Test1() { try { Monitor.Enter(obj, ref acquiredLock); for (int i = 0; i < 10; i++) { Console.WriteLine("Test1正在锁定资源"); Thread.Sleep(1000); } } catch { } finally { if (acquiredLock) Monitor.Exit(obj); Console.WriteLine("Test1已经释放资源"); } } public static void Test2() { bool isGetLock = false; Monitor.Enter(obj); try { Monitor.Enter(obj, ref acquiredLock); for (int i = 0; i < 10; i++) { Console.WriteLine("Test2正在锁定资源"); Thread.Sleep(1000); } } catch { } finally { if (acquiredLock) Monitor.Exit(obj); Console.WriteLine("Test2已经释放资源"); } } 设置获取锁的时效

如果对象已经被锁定,另一个线程使用 Monitor.Enter 对象,就会一直等待另一个线程解除锁定。

但是,如果一个线程发生问题或者出现死锁的情况,锁一直被锁定呢?或者线程具有时效性,超过一段时间不执行,已经没有了意义呢?

我们可以通过 Monitor.TryEnter() 来设置等待时间,超过一段时间后,如果锁还没有释放,就会返回 false。

改造上面的示例如下:

private static object obj = new object(); private static bool acquiredLock = false; static void Main(string[] args) { new Thread(Test1).Start(); Thread.Sleep(1000); new Thread(Test2).Start(); } public static void Test1() { try { Monitor.Enter(obj, ref acquiredLock); for (int i = 0; i < 10; i++) { Console.WriteLine("Test1正在锁定资源"); Thread.Sleep(1000); } } catch { } finally { if (acquiredLock) Monitor.Exit(obj); Console.WriteLine("Test1已经释放资源"); } } public static void Test2() { bool isGetLock = false; isGetLock = Monitor.TryEnter(obj, 500); if (isGetLock == false) { Console.WriteLine("锁还没有释放,我不干活了"); return; } try { Monitor.Enter(obj, ref acquiredLock); for (int i = 0; i < 10; i++) { Console.WriteLine("Test2正在锁定资源"); Thread.Sleep(1000); } } catch { } finally { if (acquiredLock) Monitor.Exit(obj); Console.WriteLine("Test2已经释放资源"); } }

到此这篇关于C#多线程系列之多线程锁lock和Monitor的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持易知道(ezd.cc)。

推荐阅读

    字符库快捷键|字符串快捷键

    字符库快捷键|字符串快捷键,,1. 字符串快捷键1、单行注释单行注释是 #Mac的快捷键是 command+/windows的快捷键是 Ctrl + /2、多行注

    设置线程名称|tomcat线程名称设置

    设置线程名称|tomcat线程名称设置,,1. tomcat线程名称设置一.tomcat的优化1.tomcat的自身调优采用动静分离调优Tomcat线程池调优Tomcat的

    多线程cpu电脑|多线程的CPU

    多线程cpu电脑|多线程的CPU,,1. 多线程的CPU四核心四线程,表示这个电脑的CPU核心是4个核心、4个线程的。电脑CPU的核心数量和线程数量越多,

    Python之可迭代对象、迭代器、生成器

    Python之可迭代对象、迭代器、生成器,迭代,生成器,一、概念描述可迭代对象就是可以迭代的对象,我们可以通过内置的iter函数获取其迭代器,可

    应用程序对象

    应用程序对象,,应用程序对象是一个应用程序级对象,用于在所有用户之间共享信息,并且在Web应用程序运行期间可以保存数据。 应用的性质: 方法

    简单的线程池(三)

    简单的线程池(三),吞吐量,线程,◆ 概要本文中,作者针对 《简单的线程池(一)》 和 《简单的线程池(二)》 介绍的两个线程池分别进行了并发测试。基

    Java创建对象的几种方式

    Java创建对象的几种方式,对象,方法,本文目录Java创建对象的几种方式java中几种创建对象的方式1Java中创建对象的集中方式有那些JAVA创建对