Program/C#

C# 병렬처리3 _ Mutex

사막여유 2024. 11. 23. 12:52
728x90

이번에 오늘은 c#에 있는 병렬처리중 쓰레드 경합방지를 위한 Mutex 에 대해서 알고보고자 합니다.

다른 경합방지를 위한 방법 중 lock 키워드, Mointor 클래스에 대해 궁금하신 분들은 아래 링크를 먼저 봐주세요

https://opencv-master.tistory.com/177

 

C# 병렬처리2 _ lock

오늘은 c#에 있는 병렬처리중 쓰레드 경합방지를 위한 Lock에 대해서 알고보고자 합니다. Lock 키워드lock은 object 키워드와 함께 사용되는데간단하게 말하면 현재 1번 쓰레드가 사용하고있으면 2번

opencv-master.tistory.com

 

https://opencv-master.tistory.com/178

 

C# 병렬처리2 _ Monitor

오늘은 c#에 있는 병렬처리중 쓰레드 경합방지를 위한 Monitor에 대해서 알고보고자 합니다.다른 경합방지를 위한 방법 중 lock 키워드에 대해 궁금하신 분들은 아래 링크를 먼저 봐주세요https://open

opencv-master.tistory.com

 

 

Mutex 클래스

 

  • Mutex 소개

Mutex는 'Mutual Exclusion'의 줄임말로, 여러 프로세스나 쓰레드가 공유 리소스에 대한 접근을 조율할 때 사용하는
동기화 객체입니다.

간단히 말해서, Mutex는 "한번에 하나의 프로세스만 실행할 수 있도록 보장하는 잠금장치"의 역할을 합니다.

 

위 포스팅 Lock과 Monitor와 비슷한 역할을 하지만 가장 큰 차이점은 "적용범위" 입니다.

Lock, Monitor 와 Mutex간의 차이점을 서술해보면

 

  • Monitor/lock
    • 동일한 프로세스 내의 쓰레드 간 동기화
    • 메모리 내에서만 작동
    • 상대적으로 가벼움
    • 프로세스가 종료되면 자동으로 해제
  • Mutex
    • 서로 다른 프로세스 간 동기화 가능
    • 운영체제 수준에서 작동
    • Monitor/lock보다 무거움
    • 이름을 가질 수 있어 전역적으로 식별 가능

 

즉, 간단하게 말해서 서로 다른 exe 끼리의 쓰레드 동기화가 가능하다는 의미입니다

 

위 이미지를 통해 예를 들자면 남한과 북한은 각자의 나라 (프로세스 내부)만을 관리하고 있지만
UN 안전보장이사회는 모든 나라 (모든 프로세스)를 지켜보며 관리하고 있는 것과 같은 것이죠.

// 남한 프로세스
class SouthKorea {
    private static readonly object localLock = new object();
    
    public void ManageLocalArea() {
        lock(localLock) {
            // 남한 내부의 리소스만 관리
            // 북한의 상황은 알 수도 없고 관여할 수도 없음
        }
    }
}

// 북한 프로세스
class NorthKorea {
    private static readonly object localLock = new object();
    
    public void ManageLocalArea() {
        lock(localLock) {
            // 북한 내부의 리소스만 관리
            // 남한의 상황은 알 수도 없고 관여할 수도 없음
        }
    }
}
// 모든 프로세스가 공유하는 UN Mutex
using (var unMutex = new Mutex(false, "UN_SECURITY_COUNCIL")) 
{
    unMutex.WaitOne(); // 다른 프로세스의 접근을 막음
    try 
    {
        // 전역적인 규칙 적용
        // 모든 프로세스(국가)가 이 규칙을 따라야 함
        // 한 프로세스가 사용 중이면 다른 프로세스는 대기
    }
    finally 
    {
        unMutex.ReleaseMutex(); // 다음 프로세스가 접근 가능하도록 허용
    }
}

 

어떤 나라(프로세스)가 다른 나라(프로세스)를 침범하는지 혹은 규칙을 어기고있는지를
관리하고 있는 것처럼 말이죠.

 

  • Mutex의 특징 및 주요 메서드

Mutex의 특징을 살펴보겠습니다.

 

Mutex의 커널리소스에 대한 특징으로는 아래와 같습니다.
( 커널리소스란 운영체제가 직접 관리하는 중요한 시스템 자원을 말합니. )

1. 이름이 있는 Mutex가 있고 이름이 없는 Mutex

// 이름 없는 Mutex = 한 나라의 내부 규칙
var localMutex = new Mutex();  // 내부 규정

// 이름 있는 Mutex = UN 안전보장이사회의 결의안
var globalMutex = new Mutex(false, "UN_Resolution");  // 모든 나라가 알 수 있는 국제 규약

Mutex가 UN과 같다고 설명드렸지만 UN도 하나의 조직으로 내부 관리를 하듯이
Mutex도 UN 안정보장이사회의 내부 규정으로도 사용될 수 있습니다.

2. 커널 리소스(시스템 자원)사용

 

  • UN은 실제 건물과 직원들이 있는 실체가 있는 기구
  • Mutex도 실제 시스템 자원을 사용하는 실체가 있는 객체
  • 반면 lock/Monitor는 각 나라의 규칙처럼 추상적인 개념

 

 

주요 메서드로는 다음과 같이 구성되어있습니다.

Mutex()

 

  • Mutex(): 이름 없는 Mutex 인스턴스를 생성
  • Mutex(bool initiallyOwned): 초기 소유권 지정하여 Mutex 생성
  • Mutex(bool initiallyOwned, string name): 이름과 초기 소유권을 지정하여 생성
  • Mutex(bool initiallyOwned, string name, out bool createdNew): 새로 생성되었는지 여부를 출력 매개변수로 반환

WaitOne()

  • WaitOne(): Mutex를 획득할 때까지 무기한 대기
  • WaitOne(int millisecondsTimeout): 지정된 밀리초 동안 대기
  • WaitOne(TimeSpan timeout): TimeSpan으로 지정된 시간만큼 대기
  • WaitOne(int millisecondsTimeout, bool exitContext): 대기 시간과 동기화 컨텍스트 종료 여부 지정
  • 반환값: bool (true면 Mutex 획득 성공, false면 타임아웃)

ReleaseMutex()

  • 현재 스레드가 소유한 Mutex를 해제
  • 매개변수 없음
  • Mutex를 소유하지 않은 스레드가 호출하면 ApplicationException 발생

 

이렇게만 보면 이해가 어려우니 예제를 보면서 설명해보겠습니다.

 

여러개의 폼창을 열어서 하나의 로그파일에 접근해 적어보는 예제입니다.

public Form4()
{
    InitializeComponent();
    InitializeCustomComponents();

    // 폼이 생성될 때마다 새로운 Mutex 인스턴스를 얻으려고 시도
    mutex = new Mutex(false, "Global\\BlogTestMutex", out createdNew);

    // Mutex 생성 여부 로그에 표시
    logBox.AppendText($"새로운 Mutex 생성됨: {createdNew}\r\n");

    if (createdNew)
        logBox.AppendText("이 폼이 첫 번째로 Mutex를 생성했습니다.\r\n");
    else
        logBox.AppendText("기존에 생성된 Mutex를 가져왔습니다.\r\n");
}

위 코드와 같이 하나의 폼창의 생성자마다 Mutex를 만들지만 만약 기존에 Mutex가 있는 경우에는
해당 핸들만 가져오고, 새로 핸들을 만들어서 뺏어온다거나 하지는 않습니다.

 

private void WriteButton_Click(object sender, EventArgs e)
{
    if (string.IsNullOrWhiteSpace(textBox.Text))
    {
        MessageBox.Show("메시지를 입력하세요.");
        return;
    }

    writeButton.Enabled = false;  // 버튼 비활성화
    string message = textBox.Text;  // 메시지 미리 저장

    Thread thread = new Thread(() =>
    {
        WriteLog(message);
    });
    thread.Start();
}

private void WriteLog(string message)
{
    try
    {
        // 3초 안에 Mutex를 획득하려고 시도
        if (mutex.WaitOne(TimeSpan.FromSeconds(3)))
        {
            try
            {
                string logPath = "C:\\Temp\\test_log.txt";
                string logMessage = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] (Form: {this.Handle}) {message}";

                // 로그 파일에 쓰기
                File.AppendAllText(logPath, logMessage + Environment.NewLine);

                // UI 업데이트
                UpdateLogBox($"{logMessage}\r\n");
                ClearTextBox();

                // 실제 파일 작성을 시뮬레이션하기 위한 지연
                Thread.Sleep(1000);
            }
            finally
            {
                mutex.ReleaseMutex();
            }
        }
        else
        {
            UpdateLogBox("시간 초과! 다른 프로세스가 파일을 사용 중입니다.\r\n");
        }
    }
    catch (Exception ex)
    {
        UpdateLogBox($"에러 발생: {ex.Message}\r\n");
    }
    finally
    {
        EnableWriteButton();
    }
}

 

이후 위 코드와 같이 "로그작성"이라는 버튼을 누르면 새로운 스레드를 만들어 Log에 textbox에 있는 내용을 작성할 수 있도록 매서드를 실행시킵니다.

그런데 해당 Log에서 다른 스레드가 보고있을 수 있으니, mutex.WaitOne(TimeSpan.FromSeconds(3)) 라는 메서드를 사용하여 3초동안 원하는 Mutex를 가져오려고 대기하고 있는 것이죠 

4개의 프로그램 모두 호출한 시점부터 3초를 기다리게되는 것이고 만약 그 이상의 타임아웃이 발생하면 코드상으로 
시간초과 로그를 띄우게 되는 것이죠.

 

결과 로그 이미지

결론적으로 보면 정말 단순합니다.

Mutex를 전역 동기화 객체를 하나 만든뒤에 다른 프로그램에서 모두 그 이름을 가진 동기화 객체를
확인하고 순서대로 사용하는 것 입니다.

 

마지막으로 가장 많이 사용하는 Mutex 예제를 보고 마무리하겠습니다.

Mutex에서 가장 많이 사용하는 예제는 같은 프로그램이 2번 켜지지 않도록 방지하는 역할입니다.

public Form5()
{
    bool createNew;

    // 프로그램 시작 시 Mutex 생성 시도
    // 여기서는 프로그램 실행 여부 확인용이므로 true로 설정 (초기 소유권 획득)
    singleInstanceMutex = new Mutex(true, "Global\\BlogTestSingleInstance", out createNew);

    // 이미 프로그램이 실행 중이라면
    if (!createNew)
    {
        MessageBox.Show("프로그램이 이미 실행 중입니다!", "알림",
            MessageBoxButtons.OK, MessageBoxIcon.Warning);

        // Mutex 해제
        singleInstanceMutex.Close();
        singleInstanceMutex = null;

        // 프로그램 종료
        Environment.Exit(0);
        return;
    }

    InitializeComponent();
    InitializeCustomComponents();
}

 

 

 

 

728x90

'Program > C#' 카테고리의 다른 글

C# 디스크I/O  (1) 2024.12.07
C# _ Interface  (0) 2024.11.24
C# 병렬처리2 _ Monitor  (0) 2024.11.22
C# 병렬처리1 _ lock  (0) 2024.11.20
C# Bitmap  (1) 2024.11.18