본문 바로가기
Computer Science

C# 동기화 기법: lock, Mutex, Semaphore

by 대박플머 2024. 7. 29.

동시성 프로그래밍에서는 여러 스레드가 동일한 리소스에 접근할 때 발생할 수 있는 문제를 방지하기 위해 다양한 동기화 기법을 사용합니다. C#에서는 lock, Mutex, Semaphore와 같은 동기화 메커니즘을 제공합니다. 이 글에서는 각각의 동기화 기법에 대해 설명하고, 예제 코드를 통해 어떻게 사용하는지 알아보겠습니다.

C# 프로젝트 생성

먼저, Visual Studio를 사용하여 새로운 C# 콘솔 애플리케이션 프로젝트를 생성합니다.

  1. Visual Studio를 엽니다.
  2. "Create a new project"를 클릭합니다.
  3. "Console App (.NET Core)"를 선택하고 "Next"를 클릭합니다.
  4. 프로젝트 이름과 위치를 지정한 후 "Create"를 클릭합니다.

lock 문법

lock은 C#에서 제공하는 간단한 상호 배제 기능입니다. 주로 Monitor 클래스를 사용하여 구현되며, 단일 프로세스 내에서 특정 코드 블록의 동시 실행을 방지합니다.

lock 사용 예제

다음 예제에서는 여러 스레드가 동시에 증가시키는 공유 변수의 일관성을 유지하기 위해 lock을 사용합니다.

using System;
using System.Threading;

class Program
{
    private static readonly object lockObj = new object();
    private static int counter = 0;

    static void Main()
    {
        for (int i = 0; i < 5; i++)
        {
            Thread t = new Thread(new ThreadStart(IncrementCounter));
            t.Start();
        }
    }

    private static void IncrementCounter()
    {
        for (int i = 0; i < 10; i++)
        {
            lock (lockObj)
            {
                counter++;
                Console.WriteLine("Thread {0} incremented counter to {1}", Thread.CurrentThread.ManagedThreadId, counter);
            }
            Thread.Sleep(100); // 다른 작업 수행
        }
    }
}

실행 결과

Thread 1 incremented counter to 1
Thread 1 incremented counter to 2
Thread 2 incremented counter to 3
Thread 2 incremented counter to 4
Thread 3 incremented counter to 5
Thread 3 incremented counter to 6
Thread 4 incremented counter to 7
Thread 4 incremented counter to 8
Thread 5 incremented counter to 9
Thread 5 incremented counter to 10
...

위 코드에서 lock 문은 lockObj 객체를 사용하여 코드 블록을 잠그고 다른 스레드가 동시에 접근하지 못하도록 합니다.

뮤텍스 (Mutex)

뮤텍스는 상호 배제를 위한 동기화 객체로, 하나의 스레드만 리소스를 접근할 수 있도록 합니다. 뮤텍스는 전역적으로 사용될 수 있어, 여러 프로세스 간에도 사용 가능합니다.

뮤텍스 사용 예제

다음 예제에서는 여러 스레드가 동일한 파일에 동시에 쓰기를 시도할 때 데이터 손실이나 손상을 방지하기 위해 뮤텍스를 사용합니다.

using System;
using System.IO;
using System.Threading;

class Program
{
    private static Mutex mutex = new Mutex();
    private const string filePath = "example.txt";

    static void Main()
    {
        for (int i = 0; i < 5; i++)
        {
            Thread t = new Thread(new ThreadStart(WriteToFile));
            t.Start();
        }
    }

    private static void WriteToFile()
    {
        mutex.WaitOne(); // 뮤텍스를 잠금
        try
        {
            using (StreamWriter writer = new StreamWriter(filePath, true))
            {
                writer.WriteLine("Thread {0} is writing to the file.", Thread.CurrentThread.ManagedThreadId);
            }
        }
        finally
        {
            mutex.ReleaseMutex(); // 뮤텍스를 해제
        }
    }
}

실행 결과

파일 example.txt에는 다음과 같은 내용이 작성됩니다:

Thread 1 is writing to the file.
Thread 2 is writing to the file.
Thread 3 is writing to the file.
Thread 4 is writing to the file.
Thread 5 is writing to the file.

각 스레드는 뮤텍스를 사용하여 파일 쓰기 작업을 상호 배제하여 안전하게 수행합니다.

세마포어 (Semaphore)

세마포어는 뮤텍스와 비슷하지만, 더 복잡한 동기화 기법입니다. 세마포어는 특정 리소스에 접근할 수 있는 스레드의 수를 제어합니다. 이는 리소스에 동시에 여러 스레드가 접근할 수 있게 하며, 이를 통해 보다 정교한 동기화가 가능합니다.

세마포어 사용 예제

다음 예제에서는 데이터베이스 연결 풀의 최대 연결 수를 제어하여 과부하를 방지하기 위해 세마포어를 사용합니다.

using System;
using System.Threading;

class Program
{
    private static Semaphore semaphore = new Semaphore(3, 3); // 최대 3개의 스레드가 동시에 접근 가능

    static void Main()
    {
        for (int i = 0; i < 10; i++)
        {
            Thread t = new Thread(new ThreadStart(AccessDatabase));
            t.Start();
        }
    }

    private static void AccessDatabase()
    {
        Console.WriteLine("{0} is requesting the database connection", Thread.CurrentThread.ManagedThreadId);
        semaphore.WaitOne(); // 세마포어 카운트를 감소시키며 잠금
        try
        {
            Console.WriteLine("{0} has entered the database connection pool", Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(1000); // 데이터베이스 작업 수행
        }
        finally
        {
            Console.WriteLine("{0} is leaving the database connection pool", Thread.CurrentThread.ManagedThreadId);
            semaphore.Release(); // 세마포어 카운트를 증가시키며 해제
        }
    }
}

실행 결과

1 is requesting the database connection
1 has entered the database connection pool
2 is requesting the database connection
2 has entered the database connection pool
3 is requesting the database connection
3 has entered the database connection pool
4 is requesting the database connection
5 is requesting the database connection
6 is requesting the database connection
1 is leaving the database connection pool
4 has entered the database connection pool
2 is leaving the database connection pool
5 has entered the database connection pool
3 is leaving the database connection pool
6 has entered the database connection pool
4 is leaving the database connection pool
7 has entered the database connection pool
5 is leaving the database connection pool
8 has entered the database connection pool
6 is leaving the database connection pool
9 has entered the database connection pool
7 is leaving the database connection pool
10 has entered the database connection pool
8 is leaving the database connection pool
9 is leaving the database connection pool
10 is leaving the database connection pool

각 스레드는 세마포어를 사용하여 데이터베이스 연결 풀에 접근하며, 동시에 최대 3개의 스레드만 접근할 수 있도록 제한됩니다.

결론

이 글에서는 C#에서 제공하는 lock, Mutex, Semaphore 동기화 기법에 대해 설명하고, 각각의 예제 코드를 통해 어떻게 사용하는지 살펴보았습니다. 각 기법은 특정 상황에서 유용하게 사용될 수 있으며, 적절한 동기화 메커니즘을 선택하는 것이 중요합니다. 동시성 프로그래밍을 더욱 효율적으로 구현하기 위해 이러한 동기화 기법을 이해하고 활용해보세요.