对开发人员来说,处理关键代码部分的多线程应用程序是非常重要的。
Monitor和lock是c#语言中多线程应用程序中提供线程安全的方法(lock关键字的本质就是对Monitor的封装)。两者都提供了一种机制来确保只有一个线程同时执行代码,以避免代码功能被其他线程中断
锁
c#中 Lock关键字确保一个线程同时执行一段代码。lock关键字确保一个线程不进入代码的锁定区,而另一个线程在锁定区内。
Lock关键字是Monitor的“快捷方式”。
namespace Monitor_Lock
{
class Program
{
static readonly object _object = new object();
static void TestLock()
{
lock (_object)
{
Thread.Sleep(100);
Console.WriteLine(Environment.TickCount);
}
}
static void Main(string[] args)
{
for (int i = 0; i < 10; i++)
{
ThreadStart start = new ThreadStart(TestLock);
new Thread(start).Start();
}
Console.ReadLine();
}
}
}
输出:
这里我们看到一个静态方法“TestLock”,它在对象上使用lock语句。在新线程上多次调用TestLock方法时,每次调用该方法都会访问该锁的对象是否释放。
Main方法创建十个新线程,然后在每个线程上开始调用。方法TestLock被调用十次,但是Environment.TickCount计数器显示受保护的方法区域是按顺序执行的,大约相隔100毫秒。
如果另一个线程试图进入一个锁定的代码,它将等待,阻塞,直到对象被释放。
lock关键字通过获取给定对象的互斥锁,将语句块标记为一个临界段,执行语句,然后释放锁,
Monitor
Monitor提供了一种同步对象访问的机制。它可以通过获取一个重要的锁来实现,这样一次只有一个线程可以进入给定的代码段。Monitor与lock没有什么不同,但是Monitor类对试图访问相同代码锁的各个线程的同步提供了更多的控制。
使用Monitor可以确保不允许任何其他线程访问锁所有者正在执行的应用程序代码段,除非其他线程使用不同的锁定对象执行代码。
Monitor类有以下方法通过获取和释放锁来同步访问代码的某个区域
Enter(Object) | 在指定对象上获取排他锁。 |
---|---|
Enter(Object, Boolean) | 获取指定对象上的排他锁,并自动设置一个值,指示是否获取了该锁。 |
Exit(Object) | 释放指定对象上的排他锁。 |
IsEntered(Object) | 确定当前线程是否保留指定对象锁。 |
Pulse(Object) | 通知等待队列中的线程锁定对象状态的更改。 |
PulseAll(Object) | 通知所有的等待线程对象状态的更改。 |
TryEnter(Object, TimeSpan, Boolean) | 在指定的一段时间内尝试获取指定对象上的排他锁,并自动设置一个值,指示是否获得了该锁。 |
TryEnter(Object, Int32, Boolean) | 在指定的毫秒数内尝试获取指定对象上的排他锁,并自动设置一个值,指示是否获取了该锁。 |
TryEnter(Object, TimeSpan) | 在指定的时间内尝试获取指定对象上的排他锁。 |
TryEnter(Object, Boolean) | 尝试获取指定对象上的排他锁,并自动设置一个值,指示是否获取了该锁。 |
TryEnter(Object) | 尝试获取指定对象的排他锁。 |
TryEnter(Object, Int32) | 在指定的毫秒数内尝试获取指定对象上的排他锁。 |
Wait(Object, Int32, Boolean) | 释放对象上的锁并阻止当前线程,直到它重新获取该锁。 如果已用指定的超时时间间隔,则线程进入就绪队列。 此方法还指定是否在等待之前退出上下文的同步域(如果处于同步上下文中的话)然后重新获取该同步域。 |
Wait(Object) | 释放对象上的锁并阻止当前线程,直到它重新获取该锁。 |
Wait(Object, Int32) | 释放对象上的锁并阻止当前线程,直到它重新获取该锁。 如果已用指定的超时时间间隔,则线程进入就绪队列。 |
Wait(Object, TimeSpan) | 释放对象上的锁并阻止当前线程,直到它重新获取该锁。 如果已用指定的超时时间间隔,则线程进入就绪队列。 |
Wait(Object, TimeSpan, Boolean) | 释放对象上的锁并阻止当前线程,直到它重新获取该锁。 如果已用指定的超时时间间隔,则线程进入就绪队列。 可以在等待之前退出同步上下文的同步域,随后重新获取该域。 |
Monitor锁定对象(即引用类型),而不是值类型。虽然您可以传递一个值类型来进入和退出,但是对于每个调用,它都是单独装箱的。
Wait在锁被持有并等待被通知时释放锁。当Wait被通知时,它返回并再次获得锁。Pulse和PulseAll都为等待队列中的下一个线程的开始发出信号。
下面是使用Monitor的语法。
try
{
int x = 1;
Monitor.Enter(x);
try
{
// Code that needs to be protected by the monitor.
}
finally
{
Monitor.Exit(x);
}
}
catch (SynchronizationLockException SyncEx)
{
Console.WriteLine("A SynchronizationLockException occurred. Message:");
Console.WriteLine(SyncEx.Message);
}
简单例子:
namespace Monitor_Lock
{
class Program
{
static readonly object _object = new object();
public static void PrintNumbers()
{
Monitor.Enter(_object);
try
{
for (int i = 0; i < 5; i++)
{
Thread.Sleep(100);
Console.Write(i + ",");
}
Console.WriteLine();
}
finally
{
Monitor.Exit(_object);
}
}
static void TestLock()
{
lock (_object)
{
Thread.Sleep(100);
Console.WriteLine(Environment.TickCount);
}
}
static void Main(string[] args)
{
Thread[] Threads = new Thread[3];
for (int i = 0; i < 3; i++)
{
Threads[i] = new Thread(new ThreadStart(PrintNumbers));
Threads[i].Name = "Child " + i;
}
foreach (Thread t in Threads)
t.Start();
Console.ReadLine();
}
}
}
输出:
在c# 4.0中,Monitor.Enter(_object,ref _lockTaken)重载函数获取一个独占锁和指定的对象,并自动设置一个值,该值指示锁是否被获取。
class Program
{
static readonly object _object = new object();
public static void PrintNumbers()
{
Boolean _lockTaken = false;
Monitor.Enter(_object,ref _lockTaken);
try
{
for (int i = 0; i < 5; i++)
{
Thread.Sleep(100);
Console.Write(i + ",");
}
Console.WriteLine();
}
finally
{
if (_lockTaken)
{
Monitor.Exit(_object);
}
}
}
}
与lock等价的Monitor实现
Monitor.Enter(object);
try
{
// Your code here...
}
finally
{
Monitor.Exit(object);
}
结论
Monitor类是一个静态类,不能创建它的实例。
Monitor类对象使用 Monitor.TryEnter, and Monitor.Exit 方法。一旦锁定了代码区域,就可以使用 Monitor.Wait, Monitor.Pulse, and Monitor.PulseAll 等方法。
Lock和monitor在多线程中基本上用于相同的目的,Monitor的不同之处在于,当我们希望对运行特定代码段的多个线程的同步进行更多控制时更有效