Мониторы
Обновлен: Июль 2008
Объекты Monitor предоставляют другим приложениям возможности синхронизации доступа к областям кода путем установки и снятия блокировки определенных объектов с помощью методов Monitor.Enter, Monitor.TryEnter и Monitor.Exit . Методы Monitor.Wait, Monitor.Pulse и Monitor.PulseAll можно использовать, как только для области кода установлена блокировка. Метод Wait снимает блокировку, если она установлена, и ожидает оповещения. При получении оповещения метод Wait снова возвращается и получает блокировку. Pulse и PulseAll сигнализируют о переходе к следующему потоку в очереди ожидания.
Инструкции SyncLock в Visual Basic и lock в C# используют метод Monitor.Enter для установки блокировки и метод Monitor.Exit для ее снятия. Преимущество использования инструкций языка заключается в том, что все содержимое инструкции lock или SyncLock включается в инструкцию Try. Инструкция Try снабжается блоком Finally, гарантирующим снятие блокировки.
Monitor блокирует объекты (со ссылочным типом), а не типы значений. Поскольку в методы Enter и Exit можно передать тип значения, он упаковывается отдельно для каждого вызова. При каждом вызове создается отдельный объект, поэтому метод Enter никогда не блокируется и код, защищаемый этим методом, на самом деле не является синхронизованным. Кроме того, объект, передаваемый методу Exit, отличается от объекта, передаваемого методу Enter, поэтому Monitor создает исключение SynchronizationLockException с сообщением "Метод синхронизации объекта вызван из несинхронизованного блока кода". В следующем примере показаны эти неполадки.
Private x As Integer
' The next line creates a generic object containing the value of
' x each time the code is executed, so that Enter never blocks.
Monitor.Enter(x)
Try
' Code that needs to be protected by the monitor.
Finally
' Always use Finally to ensure that you exit the Monitor.
' The following line creates another object containing
' the value of x, and throws SynchronizationLockException
' because the two objects do not match.
Monitor.Exit(x)
End Try
private int x;
// The next line creates a generic object containing the value of
// x each time the code is executed, so that Enter never blocks.
Monitor.Enter(x);
try {
// Code that needs to be protected by the monitor.
}
finally {
// Always use Finally to ensure that you exit the Monitor.
// The following line creates another object containing
// the value of x, and throws SynchronizationLockException
// because the two objects do not match.
Monitor.Exit(x);
}
Несмотря на возможность упаковки переменной типа значения перед вызовом методов Enter и Exit, как показано в следующем примере, и передачи этого значения обоим методам, этот способ не удобен. Изменение переменной никак не отражается на ее упакованной копии, и копию невозможно изменить.
Private o As Object = x
private Object o = x;
Необходимо упомянуть о различиях в использовании объектов Monitor и WaitHandle. Объекты Monitor являются полностью управляемыми и перемещаемыми. Кроме того, они более эффективны в отношении требований к ресурсам операционной системы. Объекты WaitHandle представляют объекты ожидания операционной системы и используются при синхронизации управляемого и неуправляемого кода, они также предоставляют другим приложениям дополнительные возможности операционной системы, например возможность ожидания сразу большого количества объектов.
В следующем примере кода показано комбинированное использование класса Monitor (реализованного с инструкциями компилятора lock и SyncLock), класса Interlocked и класса AutoResetEvent.
Imports System
Imports System.Threading
Imports Microsoft.VisualBasic
' Note: The class whose internal public member is the synchronizing method
' is not public; none of the client code takes a lock on the Resource object.
' The member of the nonpublic class takes the lock on itself. Written this
' way, malicious code cannot take a lock on a public object.
Class SyncResource
Public Sub Access(threadNum As Int32)
' Uses Monitor class to enforce synchronization.
SyncLock Me
' Synchronized: Despite the next conditional, each thread
' waits on its predecessor.
If threadNum Mod 2 = 0 Then
Thread.Sleep(2000)
End If
Console.WriteLine("Start Synched Resource access (Thread={0})", threadNum)
Thread.Sleep(200)
Console.WriteLine("Stop Synched Resource access (Thread={0})", threadNum)
End SyncLock
End Sub 'Access
End Class 'SyncResource
' Without the lock, the method is called in the order in which
' threads reach it.
Class UnSyncResource
Public Sub Access(threadNum As Int32)
' Does not use Monitor class to enforce synchronization.
' The next call throws the thread order.
If threadNum Mod 2 = 0 Then
Thread.Sleep(2000)
End If
Console.WriteLine("Start UnSynched Resource access (Thread={0})", threadNum)
Thread.Sleep(200)
Console.WriteLine("Stop UnSynched Resource access (Thread={0})", threadNum)
End Sub 'Access
End Class 'UnSyncResource
Public Class App
Private Shared numAsyncOps As Int32 = 5
Private Shared asyncOpsAreDone As New AutoResetEvent(False)
Private Shared SyncRes As New SyncResource()
Private Shared UnSyncRes As New UnSyncResource()
Private Shared threadNum As Int32
Public Shared Sub Main()
For threadNum = 0 To 4
ThreadPool.QueueUserWorkItem(AddressOf SyncUpdateResource, threadNum)
Next threadNum
' Wait until this WaitHandle is signaled.
asyncOpsAreDone.WaitOne()
Console.WriteLine(ControlChars.Tab + ControlChars.Lf + "All synchronized operations have completed." + ControlChars.Lf)
' Reset the thread count for unsynchronized calls.
numAsyncOps = 5
For threadNum = 0 To 4
ThreadPool.QueueUserWorkItem(AddressOf UnSyncUpdateResource, threadNum)
Next threadNum
' Wait until this WaitHandle is signaled.
asyncOpsAreDone.WaitOne()
Console.WriteLine(ControlChars.Tab + ControlChars.Cr + "All unsynchronized thread operations have completed.")
End Sub 'Main
' The callback method's signature MUST match that of
' a System.Threading.TimerCallback delegate
' (it takes an Object parameter and returns void).
Shared Sub SyncUpdateResource(state As Object)
' This calls the internal synchronized method, passing
' a thread number.
SyncRes.Access(CType(state, Int32))
' Count down the number of methods that the threads have called.
' This must be synchronized, however; you cannot know which thread
' will access the value **before** another thread's incremented
' value has been stored into the variable.
If Interlocked.Decrement(numAsyncOps) = 0 Then
asyncOpsAreDone.Set()
' Announce to Main that in fact all thread calls are done.
End If
End Sub 'SyncUpdateResource
' The callback method's signature MUST match that of
' a System.Threading.TimerCallback delegate
' (it takes an Object parameter and returns void).
Shared Sub UnSyncUpdateResource(state As [Object])
' This calls the unsynchronized method, passing
' a thread number.
UnSyncRes.Access(CType(state, Int32))
' Count down the number of methods that the threads have called.
' This must be synchronized, however; you cannot know which thread
' will access the value **before** another thread's incremented
' value has been stored into the variable.
If Interlocked.Decrement(numAsyncOps) = 0 Then
asyncOpsAreDone.Set()
' Announce to Main that in fact all thread calls are done.
End If
End Sub 'UnSyncUpdateResource
End Class 'App
using System;
using System.Threading;
// Note: The class whose internal public member is the synchronizing
// method is not public; none of the client code takes a lock on the
// Resource object.The member of the nonpublic class takes the lock on
// itself. Written this way, malicious code cannot take a lock on
// a public object.
class SyncResource {
public void Access(Int32 threadNum) {
// Uses Monitor class to enforce synchronization.
lock (this) {
// Synchronized: Despite the next conditional, each thread
// waits on its predecessor.
if (threadNum % 2 == 0)
Thread.Sleep(2000);
Console.WriteLine("Start Synched Resource access (Thread={0})", threadNum);
Thread.Sleep(200);
Console.WriteLine("Stop Synched Resource access (Thread={0})", threadNum);
}
}
}
// Without the lock, the method is called in the order in which threads reach it.
class UnSyncResource {
public void Access(Int32 threadNum) {
// Does not use Monitor class to enforce synchronization.
// The next call throws the thread order.
if (threadNum % 2 == 0)
Thread.Sleep(2000);
Console.WriteLine("Start UnSynched Resource access (Thread={0})", threadNum);
Thread.Sleep(200);
Console.WriteLine("Stop UnSynched Resource access (Thread={0})", threadNum);
}
}
public class App {
static Int32 numAsyncOps = 5;
static AutoResetEvent asyncOpsAreDone = new AutoResetEvent(false);
static SyncResource SyncRes = new SyncResource();
static UnSyncResource UnSyncRes = new UnSyncResource();
public static void Main() {
for (Int32 threadNum = 0; threadNum < 5; threadNum++) {
ThreadPool.QueueUserWorkItem(new WaitCallback(SyncUpdateResource), threadNum);
}
// Wait until this WaitHandle is signaled.
asyncOpsAreDone.WaitOne();
Console.WriteLine("\t\nAll synchronized operations have completed.\t\n");
// Reset the thread count for unsynchronized calls.
numAsyncOps = 5;
for (Int32 threadNum = 0; threadNum < 5; threadNum++) {
ThreadPool.QueueUserWorkItem(new WaitCallback(UnSyncUpdateResource), threadNum);
}
// Wait until this WaitHandle is signaled.
asyncOpsAreDone.WaitOne();
Console.WriteLine("\t\nAll unsynchronized thread operations have completed.");
}
// The callback method's signature MUST match that of a
// System.Threading.TimerCallback delegate (it takes an Object
// parameter and returns void).
static void SyncUpdateResource(Object state) {
// This calls the internal synchronized method, passing
// a thread number.
SyncRes.Access((Int32) state);
// Count down the number of methods that the threads have called.
// This must be synchronized, however; you cannot know which thread
// will access the value **before** another thread's incremented
// value has been stored into the variable.
if (Interlocked.Decrement(ref numAsyncOps) == 0)
asyncOpsAreDone.Set();
// Announce to Main that in fact all thread calls are done.
}
// The callback method's signature MUST match that of a
// System.Threading.TimerCallback delegate (it takes an Object
// parameter and returns void).
static void UnSyncUpdateResource(Object state) {
// This calls the unsynchronized method, passing a thread number.
UnSyncRes.Access((Int32) state);
// Count down the number of methods that the threads have called.
// This must be synchronized, however; you cannot know which thread
// will access the value **before** another thread's incremented
// value has been stored into the variable.
if (Interlocked.Decrement(ref numAsyncOps) == 0)
asyncOpsAreDone.Set();
// Announce to Main that in fact all thread calls are done.
}
}
См. также
Ссылки
Другие ресурсы
Объекты и функциональные возможности работы с потоками
Журнал изменений
Дата |
Журнал |
Причина |
---|---|---|
Июль 2008 |
Добавлено пояснение: в инструкциях SyncLock и lock используются методы Monitor.Enter и Exit. |
Обратная связь от клиента. |