본문 바로가기
.NET

System.Threading Monitor Class 예제 분석

by leo21c 2023. 10. 24.

Monitor 클래스 (System.Threading) | 마이크로소프트 런 (microsoft.com)

 

Monitor Class (System.Threading)

Provides a mechanism that synchronizes access to objects.

learn.microsoft.com

Monitor 클래스는 객체에 대한 액세스를 동기화 하는 매커니즘을 제공합니다.

 

Monitor 클래스를 사용해서 Random 클래스로 생성한 단일 객세의 액세스를 동기화하는 예제이다.

Monitor.Enter, Monitor.Exit 함수를 이용해서 rnd 객체를 lock, release 하는 것을 확인할 수 있다.


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

public class Example
{
   public static void Main()
   {
      List<Task> tasks = new List<Task>();//task 저장 List 객체
      Random rnd = new Random();//랜덤 넘버 함수 객체 생성
      long total = 0; //전체 합을 저장하는 변수
      int n = 0; //전체 개수를 저장하는 변수
      
      //10개의 task를 List에 추가한다.
      //각각의 task는 10,000개의 랜덤 숫자를 배열에 넣고 전체 합을 구한다.
      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;

                                      //현재 task ID, 평균, 개수를 출력한다.
                                      Console.WriteLine("Mean for task {0,2}: {1:N2} (N={2:N0})",
                                                        Task.CurrentId, (taskTotal * 1.0)/taskN,
                                                        taskN);
                                      Interlocked.Add(ref n, taskN);//전체를 개수를 n변수에 합한다.
                                      Interlocked.Add(ref total, taskTotal);//평균을 total 변수에 합한다.
                                    } ));
      try {
      	 //Task가 모두 끌날 때까지 wait을 한다.
         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)

Monitor.Enter 함수를 이용해서 rnd 함수 객체에 exclusive lock을 걸어 다른 task에서 사용할 수 없도록 한다.

모든 rand 변수가 생성이 완료된 후 Monitor.Exit 함수를 이용해서 lock을 해제(release)한다.


Interlocked.Add Method (System.Threading) | Microsoft Learn

 

Interlocked.Add Method (System.Threading)

Adds two integers and replaces the first integer with the sum, as an atomic operation.

learn.microsoft.com

멀티 쓰레딩에서 변수를 접근하여 더하기를 하기 때문에 Interlocked.Add 함수를 이용해서 n, total 변수에 lock을 걸어 합을 구하도록 처리 한다.

Interlocked.Add 함수를 이용해서 Atomic operation을 처리한다. 하나의 Task에서 Sum을 완료한 후에 다른 Task에서 Sum을 처리하기 때문에 Task의 값의 원자성을 깨지 않고 더하기를 진행한다.

 

<참고>

원자성(atomicity)은 데이터베이스 시스템에서 ACID 트랜잭션 특성중의 하나다. 하나의 원자 트랜잭션은 모두 성공하거나 또는 실패하는 데이터베이스 운용의 집합이다. 원자성의 보증은 데이터베이스의 부분적인 갱신으로 더 큰 문제가 야기되는 것을 방지한다.

원자성 (데이터베이스 시스템) - 위키백과, 우리 모두의 백과사전 (wikipedia.org)

 

원자성 (데이터베이스 시스템) - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 원자성(atomicity)은 데이터베이스 시스템에서 ACID 트랜잭션 특성 중의 하나다. 하나의 원자 트랜잭션은 모두 성공하거나 또는 실패하는 데이터베이스 운용의 집

ko.wikipedia.org


AutoResetEvent 사용 예제


using System;
using System.Threading;

// Visual Studio: Replace the default class in a Console project with 
//                the following class.
class Example
{
    //AutoResetEvent 객체 생성
    private static AutoResetEvent event_1 = new AutoResetEvent(true);
    private static AutoResetEvent event_2 = new AutoResetEvent(false);

    static void Main()
    {
        Console.WriteLine("Press Enter to create three threads and start them.\r\n" +
                          "The threads wait on AutoResetEvent #1, which was created\r\n" +
                          "in the signaled state, so the first thread is released.\r\n" +
                          "This puts AutoResetEvent #1 into the unsignaled state.");
        Console.ReadLine();
            
        for (int i = 1; i < 4; i++)
        {
            Thread t = new Thread(ThreadProc);
            t.Name = "Thread_" + i;
            t.Start();
        }
        Thread.Sleep(250);

        for (int i = 0; i < 2; i++)
        {
            Console.WriteLine("Press Enter to release another thread.");
            Console.ReadLine();
            event_1.Set();
            Thread.Sleep(250);
        }

        Console.WriteLine("\r\nAll threads are now waiting on AutoResetEvent #2.");
        for (int i = 0; i < 3; i++)
        {
            Console.WriteLine("Press Enter to release a thread.");
            Console.ReadLine();
            event_2.Set();
            Thread.Sleep(250);
        }

        // Visual Studio: Uncomment the following line.
        //Console.Readline();
    }

    static void ThreadProc()
    {
        string name = Thread.CurrentThread.Name;

        Console.WriteLine("{0} waits on AutoResetEvent #1.", name);
        event_1.WaitOne();
        Console.WriteLine("{0} is released from AutoResetEvent #1.", name);

        Console.WriteLine("{0} waits on AutoResetEvent #2.", name);
        event_2.WaitOne();
        Console.WriteLine("{0} is released from AutoResetEvent #2.", name);

        Console.WriteLine("{0} ends.", name);
    }
}

/* This example produces output similar to the following:

Press Enter to create three threads and start them.
The threads wait on AutoResetEvent #1, which was created
in the signaled state, so the first thread is released.
This puts AutoResetEvent #1 into the unsignaled state.

Thread_1 waits on AutoResetEvent #1.
Thread_1 is released from AutoResetEvent #1.
Thread_1 waits on AutoResetEvent #2.
Thread_3 waits on AutoResetEvent #1.
Thread_2 waits on AutoResetEvent #1.
Press Enter to release another thread.

Thread_3 is released from AutoResetEvent #1.
Thread_3 waits on AutoResetEvent #2.
Press Enter to release another thread.

Thread_2 is released from AutoResetEvent #1.
Thread_2 waits on AutoResetEvent #2.

All threads are now waiting on AutoResetEvent #2.
Press Enter to release a thread.

Thread_2 is released from AutoResetEvent #2.
Thread_2 ends.
Press Enter to release a thread.

Thread_1 is released from AutoResetEvent #2.
Thread_1 ends.
Press Enter to release a thread.

Thread_3 is released from AutoResetEvent #2.
Thread_3 ends.
 */