이번에 오늘은 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();
}
'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 |