Monitor

업데이트: 2008년 7월

Monitor 개체는 Monitor.Enter, Monitor.TryEnterMonitor.Exit 메서드를 통해 특정 개체에 대한 잠금을 설정 및 해제하여 코드 영역에 대한 액세스를 동기화할 수 있는 기능을 노출합니다. 코드 영역에 잠금을 설정하면 Monitor.Wait, Monitor.PulseMonitor.PulseAll 메서드를 사용할 수 있습니다. Wait는 잠금이 설정되어 있을 경우 이를 해제한 다음 알림을 받을 때까지 기다립니다. Wait에서 알림을 받으면 돌아가서 잠금을 다시 설정합니다. PulsePulseAll은 모두 대기 큐의 다음 스레드가 진행되도록 신호를 보냅니다.

Visual Basic SyncLock 및 C# lock 명령문에서는 Monitor.Enter를 사용하여 잠금을 가져오고 Monitor.Exit를 사용하여 잠금을 해제합니다. 언어 명령문을 사용할 때의 장점은 lock 또는 SyncLock 블록의 모든 내용이 Try 명령문에 포함된다는 것입니다. Try 명령문에는 잠금을 항상 해제하는 Finally 블록이 있습니다.

Monitor는 값 형식이 아닌 개체(즉, 참조 형식)를 잠급니다. EnterExit에 하나의 값 형식을 전달할 수 있지만 값 형식은 각 호출에 대해 개별적으로 Boxed 형식이 됩니다. 호출마다 별도의 개체가 만들어지므로 Enter는 차단되지 않으며 보호 중인 코드는 실제로 동기화되지 않습니다. 또한 Exit로 전달된 개체는 Enter로 전달된 개체와 다르므로 Monitor는 "Object synchronization method was called from an unsynchronized block of code" 메시지를 나타내며 SynchronizationLockException을 throw합니다. 다음 예제에서는 이러한 문제점을 보여 줍니다.

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);
}

다음 예제와 같이 EnterExit을 호출하기 전에 값 형식 변수를 boxing하고 동일한 boxed 개체를 두 메서드에 전달할 수 있지만 이렇게 해도 별다른 이점은 없습니다. 변수 변경 내용이 boxed 사본에 반영되지 않으며 boxed 사본의 값을 변경할 방법도 없습니다.

Private o As Object = x
private Object o = x;

MonitorWaitHandle 개체의 차이를 아는 것이 중요합니다. 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.
   }
}

참고 항목

참조

Monitor

기타 리소스

스레딩 개체 및 기능

변경 기록

날짜

변경 내용

이유

2008년 7월

SyncLock 및 lock 명령문은 Monitor.EnterExit를 사용한다는 내용이 명시되었습니다.

고객 의견