다음을 통해 공유


System.Threading.Monitor 클래스

이 문서는 이 API에 대한 참조 설명서를 보충하는 추가 설명을 제공합니다.

클래스 Monitor 를 사용하면 , 및 Monitor.Enter 메서드를 호출Monitor.TryEnterMonitor.Exit하여 특정 개체에 대한 잠금을 가져오고 해제하여 코드 영역에 대한 액세스를 동기화할 수 있습니다. 개체 잠금은 일반적으로 중요한 섹션이라고 하는 코드 블록에 대한 액세스를 제한하는 기능을 제공합니다. 스레드가 개체에 대한 잠금을 소유하는 동안 다른 스레드는 해당 잠금을 획득할 수 없습니다. 다른 스레드가 다른 잠긴 개체를 사용하여 코드를 실행하지 않는 한 다른 스레드가 잠금 소유자가 실행하는 애플리케이션 코드 섹션에 액세스할 수 없도록 클래스를 사용할 Monitor 수도 있습니다. Monitor 클래스에는 스레드 선호도가 있으므로 잠금을 획득한 스레드는 Monitor.Exit 메서드를 호출하여 잠금을 해제해야 합니다.

개요

Monitor 에는 다음과 같은 기능이 있습니다.

  • 요청 시 개체와 연결됩니다.
  • 바인딩되지 않습니다. 즉, 모든 컨텍스트에서 직접 호출할 수 있습니다.
  • 클래스의 인스턴스를 Monitor 만들 수 없습니다. 클래스의 Monitor 메서드는 모두 정적입니다. 각 메서드는 중요한 섹션에 대한 액세스를 제어하는 동기화된 개체를 전달합니다.

비고

Monitor 클래스를 사용하여 문자열이 아닌 개체를 잠그십시오(즉, String 외의 참조 형식). 값 형식은 잠그지 마십시오. 자세한 내용은 이 문서의 뒷부분에 있는 메서드 및 Enter 섹션의 오버로드를 참조하세요.

다음 표에서는 동기화된 개체에 액세스하는 스레드에서 수행할 수 있는 작업에 대해 설명합니다.

조치 설명
Enter, TryEnter 개체에 대한 잠금을 획득합니다. 이 작업은 중요한 섹션의 시작 부분도 표시합니다. 다른 잠긴 개체를 사용하여 중요한 섹션의 지침을 실행하지 않는 한 다른 스레드는 위험 섹션에 들어갈 수 없습니다.
Wait 다른 스레드가 개체를 잠그고 액세스할 수 있도록 개체의 잠금을 해제합니다. 다른 스레드가 개체에 액세스하는 동안 호출 스레드가 대기합니다. 펄스 신호는 대기 중인 스레드에 개체의 상태 변경 내용을 알리는 데 사용됩니다.
Pulse (신호), PulseAll 하나 이상의 대기 중인 스레드에 신호를 보냅니다. 이 신호는 대기 중인 스레드에 잠긴 객체의 상태가 변경되었음을 알려주고, 잠금 소유자가 잠금을 해제할 준비가 되었음을 의미합니다. 대기 중인 스레드는 개체의 준비 큐에 배치되므로 결국 개체에 대한 잠금을 받을 수 있습니다. 스레드에 잠금이 있으면 개체의 새 상태를 확인하여 필요한 상태에 도달했는지 확인할 수 있습니다.
Exit 개체에 대한 잠금을 해제합니다. 또한 이 작업은 잠긴 개체로 보호되는 중요한 섹션의 끝을 표시합니다.

두 가지 오버로드 집합은 EnterTryEnter 메서드에 있습니다. 한 오버로드 집합에는 잠금이 획득되면 예외가 발생하더라도 원자적으로 ref로 설정되는 ByRef(C#의 경우) 또는 Boolean(Visual Basic의 경우)의 true 매개 변수가 포함되어 있습니다. 잠금이 보호하는 리소스가 일관된 상태가 아닐 수 있는 경우에도 모든 경우에 잠금을 해제하는 것이 중요한 경우 이러한 오버로드를 사용합니다.

잠금 오브젝트

Monitor 클래스는 중요한 섹션에 대한 액세스를 제어하는 개체에서 작동하는 static (Shared Visual Basic에서) 메서드로 구성됩니다. 동기화된 각 개체에 대해 다음 정보가 유지됩니다.

  • 현재 잠금을 보유하는 스레드에 대한 참조입니다.
  • 잠금을 가져올 준비가 된 스레드를 포함하는 준비 큐에 대한 참조입니다.
  • 잠긴 개체의 상태 변경 알림을 기다리는 스레드가 포함된 대기 중인 큐에 대한 참조입니다.

Monitor 는 값 형식이 아닌 개체(즉, 참조 형식)를 잠깁니다. 값 형식을 EnterExit에 전달할 수 있지만, 각각의 호출마다 별도로 박싱됩니다. 각 호출은 별도의 개체 Enter 를 만들고, 차단하지 않으며, 보호되는 코드는 실제로 동기화되지 않습니다. 또한 Exit에 전달된 개체가 Enter에 전달된 개체와 다르기 때문에 Monitor는 "개체 동기화 메서드가 동기화되지 않은 코드 블록에서 호출되었습니다."라는 메시지와 함께 SynchronizationLockException 예외를 발생시킵니다.

다음 예제에서는 이 문제를 보여 줍니다. 10개의 작업을 시작하고 각각 250밀리초 동안 대기합니다. 그런 다음 각 태스크는 카운터 변수를 업데이트합니다. 이 변수 nTasks는 실제로 시작되고 실행된 작업의 수를 계산하기 위한 것입니다. nTasks 여러 태스크에서 동시에 업데이트할 수 있는 전역 변수이므로 모니터를 사용하여 여러 태스크에서 동시에 수정하지 않도록 보호합니다. 그러나 예제의 출력에서 알 수 있듯이 각 태스크는 예외를 SynchronizationLockException throw합니다.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

public class Example1
{
    public static void Main()
    {
        int nTasks = 0;
        List<Task> tasks = new List<Task>();

        try
        {
            for (int ctr = 0; ctr < 10; ctr++)
                tasks.Add(Task.Run(() =>
                { // Instead of doing some work, just sleep.
                    Thread.Sleep(250);
                    // Increment the number of tasks.
                    Monitor.Enter(nTasks);
                    try
                    {
                        nTasks += 1;
                    }
                    finally
                    {
                        Monitor.Exit(nTasks);
                    }
                }));
            Task.WaitAll(tasks.ToArray());
            Console.WriteLine($"{nTasks} tasks started and executed.");
        }
        catch (AggregateException e)
        {
            String msg = String.Empty;
            foreach (var ie in e.InnerExceptions)
            {
                Console.WriteLine($"{ie.GetType().Name}");
                if (!msg.Contains(ie.Message))
                    msg += ie.Message + Environment.NewLine;
            }
            Console.WriteLine("\nException Message(s):");
            Console.WriteLine(msg);
        }
    }
}
// The example displays the following output:
//    SynchronizationLockException
//    SynchronizationLockException
//    SynchronizationLockException
//    SynchronizationLockException
//    SynchronizationLockException
//    SynchronizationLockException
//    SynchronizationLockException
//    SynchronizationLockException
//    SynchronizationLockException
//    SynchronizationLockException
//
//    Exception Message(s):
//    Object synchronization method was called from an unsynchronized block of code.
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks

Module Example3
    Public Sub Main()
        Dim nTasks As Integer = 0
        Dim tasks As New List(Of Task)()

        Try
            For ctr As Integer = 0 To 9
                tasks.Add(Task.Run(Sub()
                                       ' Instead of doing some work, just sleep.
                                       Thread.Sleep(250)
                                       ' Increment the number of tasks.
                                       Monitor.Enter(nTasks)
                                       Try
                                           nTasks += 1
                                       Finally
                                           Monitor.Exit(nTasks)
                                       End Try
                                   End Sub))
            Next
            Task.WaitAll(tasks.ToArray())
            Console.WriteLine("{0} tasks started and executed.", nTasks)
        Catch e As AggregateException
            Dim msg As String = String.Empty
            For Each ie In e.InnerExceptions
                Console.WriteLine("{0}", ie.GetType().Name)
                If Not msg.Contains(ie.Message) Then
                    msg += ie.Message + Environment.NewLine
                End If
            Next
            Console.WriteLine(vbCrLf + "Exception Message(s):")
            Console.WriteLine(msg)
        End Try
    End Sub
End Module
' The example displays the following output:
'    SynchronizationLockException
'    SynchronizationLockException
'    SynchronizationLockException
'    SynchronizationLockException
'    SynchronizationLockException
'    SynchronizationLockException
'    SynchronizationLockException
'    SynchronizationLockException
'    SynchronizationLockException
'    SynchronizationLockException
'
'    Exception Message(s):
'    Object synchronization method was called from an unsynchronized block of code.

각 태스크는 SynchronizationLockException 변수가 메서드 nTasks 호출되기 전에 박스화되기 때문에 Monitor.Enter 예외를 발생시킵니다. 즉, 각 메서드 호출은 다른 변수와 독립적인 별도의 변수를 전달합니다. nTasksMonitor.Exit 메서드 호출에서 다시 박싱됩니다. 이렇게 하면 서로 nTasks독립적인 10개의 새 boxed 변수와 메서드 호출에서 생성된 10개의 boxed 변수가 Monitor.Enter 만들어집니다. 코드가 이전에 잠기지 않은 새로 생성된 변수에 대한 잠금을 해제하려고 하기 때문에 예외가 발생합니다.

다음 예제와 같이 Enter 호출하기 전에 값 형식 변수를 박싱하고 동일한 박싱된 개체를 Exit 두 메서드에 전달할 수 있지만, 이 작업을 수행하는 데는 아무런 이점이 없습니다. 박스화되지 않은 변수의 변경 사항은 박스화된 복사본에 반영되지 않으며, 박스화된 복사본의 값을 변경할 방법이 없습니다.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {

      int nTasks = 0;
      object o = nTasks;
      List<Task> tasks = new List<Task>();

      try {
         for (int ctr = 0; ctr < 10; ctr++)
            tasks.Add(Task.Run( () => { // Instead of doing some work, just sleep.
                                        Thread.Sleep(250);
                                        // Increment the number of tasks.
                                        Monitor.Enter(o);
                                        try {
                                           nTasks++;
                                        }
                                        finally {
                                           Monitor.Exit(o);
                                        }
                                      } ));
         Task.WaitAll(tasks.ToArray());
         Console.WriteLine($"{nTasks} tasks started and executed.");
      }
      catch (AggregateException e) {
         String msg = String.Empty;
         foreach (var ie in e.InnerExceptions) {
            Console.WriteLine($"{ie.GetType().Name}");
            if (! msg.Contains(ie.Message))
               msg += ie.Message + Environment.NewLine;
         }
         Console.WriteLine("\nException Message(s):");
         Console.WriteLine(msg);
      }
   }
}
// The example displays the following output:
//        10 tasks started and executed.
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks

Module Example2
    Public Sub Main()
        Dim nTasks As Integer = 0
        Dim o As Object = nTasks
        Dim tasks As New List(Of Task)()

        Try
            For ctr As Integer = 0 To 9
                tasks.Add(Task.Run(Sub()
                                       ' Instead of doing some work, just sleep.
                                       Thread.Sleep(250)
                                       ' Increment the number of tasks.
                                       Monitor.Enter(o)
                                       Try
                                           nTasks += 1
                                       Finally
                                           Monitor.Exit(o)
                                       End Try
                                   End Sub))
            Next
            Task.WaitAll(tasks.ToArray())
            Console.WriteLine("{0} tasks started and executed.", nTasks)
        Catch e As AggregateException
            Dim msg As String = String.Empty
            For Each ie In e.InnerExceptions
                Console.WriteLine("{0}", ie.GetType().Name)
                If Not msg.Contains(ie.Message) Then
                    msg += ie.Message + Environment.NewLine
                End If
            Next
            Console.WriteLine(vbCrLf + "Exception Message(s):")
            Console.WriteLine(msg)
        End Try
    End Sub
End Module
' The example displays the following output:
'       10 tasks started and executed.

동기화할 개체를 선택할 때는 프라이빗 또는 내부 개체에만 설정해야 합니다. 관련 없는 코드가 다른 용도로 잠글 동일한 개체를 선택할 수 있으므로 외부 개체를 잠그면 교착 상태가 발생할 수 있습니다.

개체가 MarshalByRefObject에서 파생된 경우, 잠금에 사용된 개체를 여러 애플리케이션 도메인에서 동기화할 수 있습니다.

중요 섹션

EnterExit 메서드를 사용하여 중요한 섹션의 시작과 끝을 표시합니다.

비고

Enter 메서드에서 제공되는 Exit 기능은 C#의 lock 문과 Visual Basic의 SyncLock 문에서 제공하는 기능과 동일하지만, 언어 구문이 Monitor.Enter(Object, Boolean) 메서드 오버로드와 Monitor.Exit 메서드를 try으로 래핑한다는 점에서 차이가 있습니다. finally 차단하여 모니터가 해제되었는지 확인합니다.

중요한 섹션이 연속 명령 집합인 경우 메서드에서 획득한 Enter 잠금은 단일 스레드만 잠긴 개체로 묶인 코드를 실행할 수 있도록 보장합니다. 이 경우 해당 코드를 try 블록에 배치하고 메서드 Exit 호출을 finally 블록에 배치하는 것이 좋습니다. 이렇게 하면 예외가 발생하더라도 잠금이 해제됩니다. 다음 코드 조각에서는 이 패턴을 보여 줍니다.

// Define the lock object.
var obj = new Object();

// Define the critical section.
Monitor.Enter(obj);
try
{
    // Code to execute one thread at a time.
}
// catch blocks go here.
finally
{
    Monitor.Exit(obj);
}
' Define the lock object.
Dim obj As New Object()

' Define the critical section.
Monitor.Enter(obj)
Try
    ' Code to execute one thread at a time.

    ' catch blocks go here.
Finally
    Monitor.Exit(obj)
End Try

이 기능은 일반적으로 클래스의 정적 또는 인스턴스 메서드에 대한 액세스를 동기화하는 데 사용됩니다.

중요한 섹션이 전체 메서드에 걸쳐 있는 경우, 메서드에 System.Runtime.CompilerServices.MethodImplAttribute를 배치하고 생성자의 Synchronized 값을 지정하여 System.Runtime.CompilerServices.MethodImplAttribute 잠금 기능을 달성할 수 있습니다. 이 특성을 사용할 때 EnterExit 메서드 호출이 필요하지 않습니다. 다음 코드 조각에서는 이 패턴을 보여 줍니다.

[MethodImplAttribute(MethodImplOptions.Synchronized)]
void MethodToLock()
{
    // Method implementation.
}
<MethodImplAttribute(MethodImplOptions.Synchronized)>
Sub MethodToLock()
    ' Method implementation.
End Sub

이 특성은 메서드가 반환될 때까지 현재 스레드가 잠금을 유지하도록 합니다. 잠금을 더 빨리 해제할 수 있으면 특성 대신 클래스, C# Monitor 문 또는 메서드 내부의 Visual Basic SyncLock 문을 사용합니다.

지정된 개체를 잠그고 해제하는 Enter 문과 Exit 문장이 멤버 또는 클래스 경계를 넘거나 둘 다 교차할 수 있지만, 이러한 방법은 권장되지 않습니다.

Pulse, PulseAll 및 Wait

스레드가 잠금을 소유하고 있으며, 잠금이 보호하는 중요한 섹션에 들어가면 Monitor.Wait, Monitor.Pulse, 및 Monitor.PulseAll 메서드를 호출할 수 있습니다.

잠금을 보유하는 스레드가 호출 Wait되면 잠금이 해제되고 스레드가 동기화된 개체의 대기 큐에 추가됩니다. 준비 큐의 첫 번째 스레드(있는 경우)는 잠금을 획득하고 중요한 섹션에 들어갑니다. Wait를 호출한 스레드는 잠금을 보유하는 스레드에 의해 Monitor.Pulse 또는 Monitor.PulseAll 메서드가 호출되면 대기 중인 큐의 맨 앞에서 준비 큐로 이동됩니다(이동하려면 스레드가 대기 중인 큐의 맨 앞에 있어야 함). Wait 호출 스레드가 잠금을 다시 가져올 때 메서드가 반환됩니다.

잠금을 보유하는 스레드가 호출 Pulse되면 대기 중인 큐의 헤드에 있는 스레드가 준비 큐로 이동됩니다. 메서드를 PulseAll 호출하면 대기 중인 큐의 모든 스레드가 준비 큐로 이동합니다.

모니터 및 대기 핸들

Monitor 클래스와 WaitHandle 객체 사용의 차이를 주목하는 것이 중요합니다.

  • 클래스는 Monitor 순수하게 관리되고 완전히 이식 가능하며 운영 체제 리소스 요구 사항 측면에서 더 효율적일 수 있습니다.
  • WaitHandle 개체는 운영 체제 대기 가능 개체를 나타내고, 관리 코드와 관리되지 않는 코드 간의 동기화에 유용하며, 여러 개체를 한 번에 대기하는 기능과 같은 일부 고급 운영 체제 기능을 노출합니다.

예시

다음 예제에서는 Monitor 클래스를 사용하여 Random 클래스가 나타내는 난수 생성기의 단일 인스턴스에 대한 액세스를 동기화합니다. 이 예제에서는 각각 스레드 풀 스레드에서 비동기적으로 실행되는 10개의 태스크를 만듭니다. 각 태스크는 10,000개의 난수를 생성하고, 평균을 계산하고, 생성된 난수 수와 해당 합계의 실행 합계를 유지하는 두 개의 프로시저 수준 변수를 업데이트합니다. 모든 태스크가 실행된 후에는 이 두 값을 사용하여 전체 평균을 계산합니다.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

public class Example2
{
    public static void Main()
    {
        List<Task> tasks = new List<Task>();
        Random rnd = new Random();
        long total = 0;
        int n = 0;

        for (int taskCtr = 0; taskCtr < 10; taskCtr++)
            tasks.Add(Task.Run(() =>
            {
                int[] values = new int[10000];
                int taskTotal = 0;
                int taskN = 0;
                int ctr = 0;
                Monitor.Enter(rnd);
                // Generate 10,000 random integers
                for (ctr = 0; ctr < 10000; ctr++)
                    values[ctr] = rnd.Next(0, 1001);
                Monitor.Exit(rnd);
                taskN = ctr;
                foreach (var value in values)
                    taskTotal += value;

                Console.WriteLine("Mean for task {0,2}: {1:N2} (N={2:N0})",
                                  Task.CurrentId, (taskTotal * 1.0) / taskN,
                                  taskN);
                Interlocked.Add(ref n, taskN);
                Interlocked.Add(ref total, taskTotal);
            }));
        try
        {
            Task.WaitAll(tasks.ToArray());
            Console.WriteLine($"\nMean for all tasks: {(total * 1.0) / n:N2} (N={n:N0})");
        }
        catch (AggregateException e)
        {
            foreach (var ie in e.InnerExceptions)
                Console.WriteLine($"{ie.GetType().Name}: {ie.Message}");
        }
    }
}
// The example displays output like the following:
//       Mean for task  1: 499.04 (N=10,000)
//       Mean for task  2: 500.42 (N=10,000)
//       Mean for task  3: 499.65 (N=10,000)
//       Mean for task  8: 502.59 (N=10,000)
//       Mean for task  5: 502.75 (N=10,000)
//       Mean for task  4: 494.88 (N=10,000)
//       Mean for task  7: 499.22 (N=10,000)
//       Mean for task 10: 496.45 (N=10,000)
//       Mean for task  6: 499.75 (N=10,000)
//       Mean for task  9: 502.79 (N=10,000)
//
//       Mean for all tasks: 499.75 (N=100,000)
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks

Module Example4
    Public Sub Main()
        Dim tasks As New List(Of Task)()
        Dim rnd As New Random()
        Dim total As Long = 0
        Dim n As Integer = 0

        For taskCtr As Integer = 0 To 9
            tasks.Add(Task.Run(Sub()
                                   Dim values(9999) As Integer
                                   Dim taskTotal As Integer = 0
                                   Dim taskN As Integer = 0
                                   Dim ctr As Integer = 0
                                   Monitor.Enter(rnd)
                                   ' Generate 10,000 random integers.
                                   For ctr = 0 To 9999
                                       values(ctr) = rnd.Next(0, 1001)
                                   Next
                                   Monitor.Exit(rnd)
                                   taskN = ctr
                                   For Each value In values
                                       taskTotal += value
                                   Next

                                   Console.WriteLine("Mean for task {0,2}: {1:N2} (N={2:N0})",
                                                  Task.CurrentId, taskTotal / taskN,
                                                  taskN)
                                   Interlocked.Add(n, taskN)
                                   Interlocked.Add(total, taskTotal)
                               End Sub))
        Next

        Try
            Task.WaitAll(tasks.ToArray())
            Console.WriteLine()
            Console.WriteLine("Mean for all tasks: {0:N2} (N={1:N0})",
                           (total * 1.0) / n, n)
        Catch e As AggregateException
            For Each ie In e.InnerExceptions
                Console.WriteLine("{0}: {1}", ie.GetType().Name, ie.Message)
            Next
        End Try
    End Sub
End Module
' The example displays output like the following:
'       Mean for task  1: 499.04 (N=10,000)
'       Mean for task  2: 500.42 (N=10,000)
'       Mean for task  3: 499.65 (N=10,000)
'       Mean for task  8: 502.59 (N=10,000)
'       Mean for task  5: 502.75 (N=10,000)
'       Mean for task  4: 494.88 (N=10,000)
'       Mean for task  7: 499.22 (N=10,000)
'       Mean for task 10: 496.45 (N=10,000)
'       Mean for task  6: 499.75 (N=10,000)
'       Mean for task  9: 502.79 (N=10,000)
'
'       Mean for all tasks: 499.75 (N=100,000)

스레드 풀 스레드에서 실행되는 모든 작업에서 접근할 수 있기 때문에, 변수 totaln에 대한 접근은 반드시 동기화되어야 합니다. 이 Interlocked.Add 메서드는 이 용도로 사용됩니다.

다음 예제에서는 Monitor 클래스(lock 또는 SyncLock 언어 구문을 사용하여 구현), Interlocked 클래스 및 AutoResetEvent 클래스를 결합하여 사용하는 방법을 보여 줍니다. C#에서는 internal를, Visual Basic에서는 Friend를 정의하는 두 개의 클래스로서, 각각 SyncResourceUnSyncResource는 리소스에 대한 동기화된 액세스와 동기화되지 않은 액세스를 제공합니다. 이 예제에서 동기화된 액세스와 동기화되지 않은 액세스 간의 차이를 보여 주도록 하기 위해(각 메서드 호출이 빠르게 완료되는 경우) 메서드에는 임의의 지연이 포함됩니다. 속성이 짝수인 Thread.ManagedThreadId 스레드의 경우 메서드는 2,000밀리초의 지연을 도입하기 위해 호출 Thread.Sleep 합니다. 클래스가 SyncResource 공용이 아니므로 클라이언트 코드 중 어느 것도 동기화된 리소스에 대한 잠금을 사용하지 않습니다. 내부 클래스 자체는 잠금을 사용합니다. 이렇게 하면 악성 코드가 공용 개체를 잠그지 못하게 됩니다.

using System;
using System.Threading;

internal class SyncResource
{
    // Use a monitor to enforce synchronization.
    public void Access()
    {
        lock(this) {
            Console.WriteLine($"Starting synchronized resource access on thread #{Thread.CurrentThread.ManagedThreadId}");
            if (Thread.CurrentThread.ManagedThreadId % 2 == 0)
                Thread.Sleep(2000);

            Thread.Sleep(200);
            Console.WriteLine($"Stopping synchronized resource access on thread #{Thread.CurrentThread.ManagedThreadId}");
        }
    }
}

internal class UnSyncResource
{
    // Do not enforce synchronization.
    public void Access()
    {
        Console.WriteLine($"Starting unsynchronized resource access on Thread #{Thread.CurrentThread.ManagedThreadId}");
        if (Thread.CurrentThread.ManagedThreadId % 2 == 0)
            Thread.Sleep(2000);

        Thread.Sleep(200);
        Console.WriteLine($"Stopping unsynchronized resource access on thread #{Thread.CurrentThread.ManagedThreadId}");
    }
}

public class App
{
    private static int numOps;
    private static AutoResetEvent opsAreDone = new AutoResetEvent(false);
    private static SyncResource SyncRes = new SyncResource();
    private static UnSyncResource UnSyncRes = new UnSyncResource();

   public static void Main()
   {
        // Set the number of synchronized calls.
        numOps = 5;
        for (int ctr = 0; ctr <= 4; ctr++)
            ThreadPool.QueueUserWorkItem(new WaitCallback(SyncUpdateResource));

        // Wait until this WaitHandle is signaled.
        opsAreDone.WaitOne();
        Console.WriteLine("\t\nAll synchronized operations have completed.\n");

        // Reset the count for unsynchronized calls.
        numOps = 5;
        for (int ctr = 0; ctr <= 4; ctr++)
            ThreadPool.QueueUserWorkItem(new WaitCallback(UnSyncUpdateResource));

        // Wait until this WaitHandle is signaled.
        opsAreDone.WaitOne();
        Console.WriteLine("\t\nAll unsynchronized thread operations have completed.\n");
   }

    static void SyncUpdateResource(Object state)
    {
        // Call the internal synchronized method.
        SyncRes.Access();

        // Ensure that only one thread can decrement the counter at a time.
        if (Interlocked.Decrement(ref numOps) == 0)
            // Announce to Main that in fact all thread calls are done.
            opsAreDone.Set();
    }

    static void UnSyncUpdateResource(Object state)
    {
        // Call the unsynchronized method.
        UnSyncRes.Access();

        // Ensure that only one thread can decrement the counter at a time.
        if (Interlocked.Decrement(ref numOps) == 0)
            // Announce to Main that in fact all thread calls are done.
            opsAreDone.Set();
    }
}
// The example displays output like the following:
//    Starting synchronized resource access on thread #6
//    Stopping synchronized resource access on thread #6
//    Starting synchronized resource access on thread #7
//    Stopping synchronized resource access on thread #7
//    Starting synchronized resource access on thread #3
//    Stopping synchronized resource access on thread #3
//    Starting synchronized resource access on thread #4
//    Stopping synchronized resource access on thread #4
//    Starting synchronized resource access on thread #5
//    Stopping synchronized resource access on thread #5
//
//    All synchronized operations have completed.
//
//    Starting unsynchronized resource access on Thread #7
//    Starting unsynchronized resource access on Thread #9
//    Starting unsynchronized resource access on Thread #10
//    Starting unsynchronized resource access on Thread #6
//    Starting unsynchronized resource access on Thread #3
//    Stopping unsynchronized resource access on thread #7
//    Stopping unsynchronized resource access on thread #9
//    Stopping unsynchronized resource access on thread #3
//    Stopping unsynchronized resource access on thread #10
//    Stopping unsynchronized resource access on thread #6
//
//    All unsynchronized thread operations have completed.
Imports System.Threading

Friend Class SyncResource
    ' Use a monitor to enforce synchronization.
    Public Sub Access()
        SyncLock Me
            Console.WriteLine("Starting synchronized resource access on thread #{0}",
                              Thread.CurrentThread.ManagedThreadId)
            If Thread.CurrentThread.ManagedThreadId Mod 2 = 0 Then
                Thread.Sleep(2000)
            End If
            Thread.Sleep(200)
            Console.WriteLine("Stopping synchronized resource access on thread #{0}",
                              Thread.CurrentThread.ManagedThreadId)
        End SyncLock
    End Sub
End Class

Friend Class UnSyncResource
    ' Do not enforce synchronization.
    Public Sub Access()
        Console.WriteLine("Starting unsynchronized resource access on Thread #{0}",
                          Thread.CurrentThread.ManagedThreadId)
        If Thread.CurrentThread.ManagedThreadId Mod 2 = 0 Then
            Thread.Sleep(2000)
        End If
        Thread.Sleep(200)
        Console.WriteLine("Stopping unsynchronized resource access on thread #{0}",
                          Thread.CurrentThread.ManagedThreadId)
    End Sub
End Class

Public Module App
    Private numOps As Integer
    Private opsAreDone As New AutoResetEvent(False)
    Private SyncRes As New SyncResource()
    Private UnSyncRes As New UnSyncResource()

    Public Sub Main()
        ' Set the number of synchronized calls.
        numOps = 5
        For ctr As Integer = 0 To 4
            ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf SyncUpdateResource))
        Next
        ' Wait until this WaitHandle is signaled.
        opsAreDone.WaitOne()
        Console.WriteLine(vbTab + Environment.NewLine + "All synchronized operations have completed.")
        Console.WriteLine()

        numOps = 5
        ' Reset the count for unsynchronized calls.
        For ctr As Integer = 0 To 4
            ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf UnSyncUpdateResource))
        Next

        ' Wait until this WaitHandle is signaled.
        opsAreDone.WaitOne()
        Console.WriteLine(vbTab + Environment.NewLine + "All unsynchronized thread operations have completed.")
    End Sub

    Sub SyncUpdateResource()
        ' Call the internal synchronized method.
        SyncRes.Access()

        ' Ensure that only one thread can decrement the counter at a time.
        If Interlocked.Decrement(numOps) = 0 Then
            ' Announce to Main that in fact all thread calls are done.
            opsAreDone.Set()
        End If
    End Sub

    Sub UnSyncUpdateResource()
        ' Call the unsynchronized method.
        UnSyncRes.Access()

        ' Ensure that only one thread can decrement the counter at a time.
        If Interlocked.Decrement(numOps) = 0 Then
            ' Announce to Main that in fact all thread calls are done.
            opsAreDone.Set()
        End If
    End Sub
End Module
' The example displays output like the following:
'    Starting synchronized resource access on thread #6
'    Stopping synchronized resource access on thread #6
'    Starting synchronized resource access on thread #7
'    Stopping synchronized resource access on thread #7
'    Starting synchronized resource access on thread #3
'    Stopping synchronized resource access on thread #3
'    Starting synchronized resource access on thread #4
'    Stopping synchronized resource access on thread #4
'    Starting synchronized resource access on thread #5
'    Stopping synchronized resource access on thread #5
'
'    All synchronized operations have completed.
'
'    Starting unsynchronized resource access on Thread #7
'    Starting unsynchronized resource access on Thread #9
'    Starting unsynchronized resource access on Thread #10
'    Starting unsynchronized resource access on Thread #6
'    Starting unsynchronized resource access on Thread #3
'    Stopping unsynchronized resource access on thread #7
'    Stopping unsynchronized resource access on thread #9
'    Stopping unsynchronized resource access on thread #3
'    Stopping unsynchronized resource access on thread #10
'    Stopping unsynchronized resource access on thread #6
'
'    All unsynchronized thread operations have completed.

이 예제에서는 리소스에 액세스하려고 시도하는 스레드 수를 정의하는 변수 numOps를 정의합니다. 애플리케이션 스레드는 동기화 및 동기화되지 않은 액세스에 대한 메서드를 각각 5번 호출 ThreadPool.QueueUserWorkItem(WaitCallback) 합니다. 메서드에는 ThreadPool.QueueUserWorkItem(WaitCallback) 매개 변수를 허용하지 않고 값을 반환하지 않는 대리자인 단일 매개 변수가 있습니다. 동기화된 액세스의 경우 메서드를 SyncUpdateResource 호출합니다. 동기화되지 않은 액세스의 경우 메서드를 UnSyncUpdateResource 호출합니다. 각 메서드 집합이 호출된 후 애플리케이션 스레드는 AutoResetEvent.WaitOne 메서드를 호출하여 인스턴스가 신호를 보낼 때까지 AutoResetEvent 차단합니다.

메서드에 대한 각 호출은 SyncUpdateResource 내부의 SyncResource.Access 메서드를 호출하고, Interlocked.Decrement 메서드를 호출하여 numOps 카운터를 감소시킵니다. 메서드는 Interlocked.Decrement 카운터를 감소시키는 데 사용됩니다. 그렇지 않으면 첫 번째 스레드의 감소된 값이 변수에 저장되기 전에 두 번째 스레드가 값에 액세스하도록 확신할 수 없기 때문입니다. 마지막으로 동기화된 작업자 스레드가 카운터를 0으로 줄이면 동기화된 모든 스레드가 리소스에 대한 액세스를 완료했음을 나타내며, SyncUpdateResource 메서드는 메서드를 호출 EventWaitHandle.Set 하여 주 스레드가 실행을 계속하도록 신호를 줍니다.

메서드에 대한 각 호출은 UnSyncUpdateResource 내부의 UnSyncResource.Access 메서드를 호출하고, Interlocked.Decrement 메서드를 호출하여 numOps 카운터를 감소시킵니다. 다시 한 번 메서드 Interlocked.Decrement 는 첫 번째 스레드의 감소된 값이 변수에 할당되기 전에 두 번째 스레드가 값에 액세스하지 않도록 카운터를 감소시키는 데 사용됩니다. 마지막으로 동기화되지 않은 작업자 스레드가 카운터를 0으로 줄이면 더 이상 동기화되지 않은 스레드가 리소스에 액세스할 필요가 없음을 나타내며, UnSyncUpdateResource 메서드는 주 스레드가 실행을 계속하도록 신호를 표시하는 메서드를 호출 EventWaitHandle.Set 합니다.

예제의 출력에서와 같이 동기화된 액세스는 다른 스레드가 액세스하기 전에 호출 스레드가 보호된 리소스를 종료하도록 합니다. 각 스레드는 선행 작업에서 대기합니다. 반면에 잠금 UnSyncResource.Access 이 없으면 스레드가 도달하는 순서대로 메서드가 호출됩니다.