다음을 통해 공유


System.Threading.Monitor 클래스

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

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

개요

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

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

참고 항목

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

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

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

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

잠금 개체

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

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

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

다음 예제에서는 이 문제를 보여 줍니다. 각각 250밀리초 동안 절전 모드인 10개의 작업을 시작합니다. 그런 다음 각 태스크는 카운터 변수를 업데이트합니다. 이 변수 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("{0} tasks started and executed.", nTasks);
        }
        catch (AggregateException e)
        {
            String msg = String.Empty;
            foreach (var ie in e.InnerExceptions)
            {
                Console.WriteLine("{0}", 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 호출하기 전에 변수가 boxed되기 때문에 예외를 Monitor.Enter throw합니다. 즉, 각 메서드 호출은 다른 변수와 독립적인 별도의 변수를 전달합니다. nTasks 는 메서드 호출에서 다시 boxed됩니다 Monitor.Exit . 이렇게 하면 서로 nTasks독립적인 10개의 새 boxed 변수와 메서드 호출에서 생성된 10개의 boxed 변수가 Monitor.Enter 만들어집니다. 코드가 이전에 잠기지 않은 새로 만든 변수에 대한 잠금을 해제하려고 하기 때문에 예외가 throw됩니다.

다음 예제와 같이 호출 Enter 하기 전에 값 형식 변수를 상자로 지정하고 Exit동일한 boxed 개체를 두 메서드에 전달할 수 있지만 이 작업을 수행하는 데는 이점이 없습니다. 받은 편지하지 않은 변수에 대한 변경 내용은 boxed 복사본에 반영되지 않으며 상자가 있는 복사본의 값을 변경할 방법이 없습니다.

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("{0} tasks started and executed.", nTasks);
      }
      catch (AggregateException e) {
         String msg = String.Empty;
         foreach (var ie in e.InnerExceptions) {
            Console.WriteLine("{0}", 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 메서드를 사용하여 중요한 섹션의 시작과 끝을 표시합니다.

참고 항목

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

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

// 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 하고 생성자의 System.Runtime.CompilerServices.MethodImplAttribute값을 지정하여 Synchronized 잠금 기능을 달성할 수 있습니다. 이 특성을 Enter 사용하는 경우 메서드 호출 및 Exit 메서드가 필요하지 않습니다. 다음 코드 조각에서는 이 패턴을 보여 줍니다.

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

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

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

Pulse, PulseAll 및 Wait

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

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

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

모니터 및 대기 핸들

클래스와 WaitHandle 개체의 사용 간의 차이점을 유의해야 Monitor 합니다.

  • 클래스는 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: {0:N2} (N={1:N0})",
                              (total * 1.0) / n, n);
        }
        catch (AggregateException e)
        {
            foreach (var ie in e.InnerExceptions)
                Console.WriteLine("{0}: {1}", 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)

스레드 풀 스레드에서 실행되는 모든 작업에서 액세스할 수 있으므로 변수 total 에 대한 액세스도 n 동기화해야 합니다. 이 Interlocked.Add 메서드는 이 용도로 사용됩니다.

다음 예제에서는 클래스의 Monitor 결합된 사용(또는 SyncLock 언어 구문으로 lock 구현), Interlocked 클래스 및 클래스를 보여 줍니다AutoResetEvent. C#에서는 두 개의 클래스 또는 Friend (Visual Basic에서) 클래스를 정의 internal 하며UnSyncResource, SyncResource 각각 리소스에 대한 동기화 및 동기화되지 않은 액세스를 제공합니다. 이 예제에서 동기화된 액세스와 동기화되지 않은 액세스 간의 차이를 보여 주도록 하기 위해(각 메서드 호출이 빠르게 완료되는 경우) 메서드에는 임의의 지연이 포함됩니다. 속성이 짝수인 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 #{0}",
                              Thread.CurrentThread.ManagedThreadId);
            if (Thread.CurrentThread.ManagedThreadId % 2 == 0)
                Thread.Sleep(2000);

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

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

        Thread.Sleep(200);
        Console.WriteLine("Stopping unsynchronized resource access on thread #{0}",
                          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 이 없으면 스레드가 도달하는 순서대로 메서드가 호출됩니다.