동기화 : 작업들 사이의 수행 시기를 맞추는 것

  • 다수의 스레드가 동시에 공유 자원을 사용할 때 순서를 정하는 것


동기화 키워드

  • Lock
    lock(C#) 사용하면 다른 스레드의 방해를 받지 않은 채 코드 블록의 실행을 완료할 수 있습니다.
    이를 위해서는 코드 블록을 진행하는 동안 지정된 개체에 대한 상호 배타적 잠금을 유지해야 합니다.
    lock 문은 개체를 인수로 지정하며 뒤에 한 번에 하나의 스레드에서만 실행할 코드 블록이 나옵니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class TestThreading {
        private System.Object lockThis = new System.Object();
     
        public void Process()
        {
            lock (lockThis) {
                // Access thread-sensitive resources.
            }
        }
    }
    cs


  • Monitor
    lock 및 SyncLock 키워드와 마찬가지로 monitor를 사용하면 코드 블록이 여러 스레드에서 동시에 실행되지 않도록 방지할 수 있습니다.
    Enter 메서드를 사용하면 스레드 하나만 다음 문으로 진행하도록 허용할 수 있습니다.
    다른 모든 스레드는 현재 실행 중인 스레드가 Exit를 호출할 때까지 차단됩니다.

    1
    2
    3
    4
    5
    6
    7
    System.Object obj = (System.Object)x;
    System.Threading.Monitor.Enter(obj);
    try {
        DoSomething();
    finally {
        System.Threading.Monitor.Exit(obj);
    }
    cs
    lock 키워드를 사용할 때와 동일합니다.

  • Mutex
    뮤텍스는 monitor와 비슷합니다.
    이는 한 번에 여러 스레드에서 코드 블록이 동시에 실행되는 것을 방지합니다.
    사실 "뮤텍스(mutex)"라는 용어는 "상호 배타적(mutually exclusive)"이라는 표현의 줄임말입니다.
    그러나 monitor와 달리 뮤텍스를 사용하면 프로세스 간에 스레드를 동기화할 수 있습니다.
    뮤텍스는 Mutex 클래스로 표현됩니다.
    프로세스간 동기화에 사용되는 뮤텍스를 명명된 뮤텍스라고 합니다.
    이는 다른 응용 프로그램에 사용하기 위한 것이며 전역 또는 정적 변수를 통해 공유할 수 없기 때문입니다.
    두 응용 프로그램에서 모두 동일한 뮤텍스 개체에 액세스할 수 있도록 이 뮤텍스에 이름을 지정해야 합니다.
    프로세스 내의 스레드를 동기화하는 데 뮤텍스를 사용할 수도 있지만 일반적으로 Monitor를 사용하는 것이 더 좋습니다.
    monitor는 .NET Framework용으로 특별히 디자인되었으며 리소스를 더 효율적으로 활용하기 때문입니다.
    반면, Mutex 클래스는 Win32 구문에 대한 래퍼입니다.
    이는 monitor보다 더 강력하지만 뮤텍스를 사용하려면 Monitor 클래스에 필요한 것보다 더 처리가 복잡한 interop 전환이 필요합니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    using System;
    using System.Threading;
     
    namespace MainServer {
        class Program {
            static Mutex m_mutex = new Mutex();
            static int m_count;
            static void Main(string[] args)
            {
                Thread thread1 = new Thread(new ThreadStart(ThreadProc));
                thread1.Start();
     
                Thread thread2 = new Thread(new ThreadStart(ThreadProc));
                thread2.Start();
            }
     
            static void ThreadProc()
            {
                m_mutex.WaitOne();
                for (int i = 0; i < 5; i++) {
                    m_count++;
                    Console.WriteLine($"Thread ID: {Thread.CurrentThread.GetHashCode()} {m_count}");
                }
                m_mutex.ReleaseMutex();
            }
        }
    }
     
    cs


  • Interlocked
    Interlocked의 메서드를 사용하면 여러 스레드에서 같은 값을 동시에 업데이트하거나 비교하려고 할 때 발생할 수 있는 문제를 방지할 수 있습니다.
    이 클래스의 메서드를 사용하면 모든 스레드에서 값을 안전하게 늘리거나, 줄이거나, 교환하거나, 비교할 수 있습니다.

  • AutoResetEvent
    AutoResetEvent를 사용하면 스레드가 신호를 통해 서로 통신 할 수 있습니다.
    일반적으로 스레드가 리소스에 독점적으로 액세스해야하는 경우이 클래스를 사용합니다

  • ManualResetEvent
    ManualResetEvent 스레드가 신호를 보내 서로 통신할 수 있습니다.
    일반적으로이 통신 하나의 스레드가 다른 스레드에서 진행 되기 전에 완료 해야 하는 작업에 관여 합니다.

  • ReaderWriterLock
    일부 경우에는 데이터를 쓰고 있을 때만 리소스를 잠그고 데이터를 업데이트하지 않을 때는 여러 클라이언트에서 동시에 데이터를 읽을 수 있도록 할 수 있습니다.
    ReaderWriterLock 클래스를 사용하면 스레드에서 리소스를 수정하는 동안은 리소스를 단독으로 사용하고 리소스를 읽을 때는 여러 스레드에서 동시에 사용하도록 할 수 있습니다.
    ReaderWriter 잠금은 해당 스레드에서 데이터를 업데이트할 필요가 없는 경우에도 다른 스레드를 대기하도록 만드는 단독 잠금 대신 사용할 수 있는 유용한 기능입니다.

동기화 이벤트 및 대기 핸들(AutoResetEvent, ManualResetEvent)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
using System;
using System.Threading;
 
class ThreadingExample {
    static AutoResetEvent AutoEvent;
    static ManualResetEvent ManualEvent;
 
    static void DoWork1()
    {
        Console.WriteLine("DoWork1 작업자 스레드가 시작(AutoEvent 이벤트를 기다림)");
        AutoEvent.WaitOne();
        Console.WriteLine("DoWork1 작업자 스레드가 다시 활성화 됨");
        for (int i = 0; i < 10; i++) {
            Console.WriteLine($"DoWork1 작업자 중... {i}");
        }
        Console.WriteLine("DoWork1 작업자 스레드 종료");
    }
 
    static void DoWork2()
    {
        Console.WriteLine("DoWork2 작업자 스레드가 시작(ManualEvent 이벤트를 기다림)");
        ManualEvent.WaitOne();
        Console.WriteLine("DoWork2 작업자 스레드가 다시 활성화 됨");
        for (int i = 0; i < 10; i++) {
            Console.WriteLine($"DoWork2 작업자 중... {i}");
        }
        Console.WriteLine("DoWork2 작업자 스레드 종료");
    }
 
    static void Main()
    {
        AutoEvent = new AutoResetEvent(false);
        ManualEvent = new ManualResetEvent(false);
 
        Console.WriteLine("메인 스레드 시작");
        Thread t1 = new Thread(DoWork1);
        t1.Start();
        Thread t2 = new Thread(DoWork2);
        t2.Start();
 
        Console.WriteLine("메인 스레드 1초 대기");
        Thread.Sleep(1000);
 
        Console.WriteLine("메인 스레드 대기 끝(AutoEvent, ManualEvent 이벤트 호출)");
        Console.WriteLine($"AutoEvent 호출: {AutoEvent.Set()}");
        Console.WriteLine($"ManualEvent 호출: {ManualEvent.Set()}");
    }
}
 
cs


동기화 대상

  • 공유 자원에 대한 접근이 예상되는 스레드

  • 한 객체를 다수의 스레드가 사용할 때


참고자료

반응형