개발언어/C#

[C#] 스레드 동기화(Thread synchronization)-Mutex

내꺼블로그 2024. 5. 17. 15:22

스레드를 동기화하는 방법으로 Mutex 클래스도 있다.

Mutex 역시 크리티컬 섹션에 들어가는 스레드를 제어하여 동기화하는 역할을 한다. 스레드가 mutex를 획득하면 해당 스레드가 mutex를 해제할 때까지 다음 스레드는 중단된다.

Mutex는 Monitor와 달리 프로세스 간 동기화에도 사용할 수 있다. 프로세스 내에서 스레드의 동기화를 담당하는 mutex를 local mutex라 하며, 프로세스 간 동기화를 담당하는 mutex는 named mutex라고 한다.

 


 

WaitOne(), ReleaseMutex()

Mutex 클래스를 사용하여 동기화를 구현하기 위해서는 WaitOne()과 ReleaseMutex() 메서드를 알아야 한다.

WaitOne()은 스레드가 mutex에 소유권을 요청하는 메서드이다. mutex 소유권을 가진 메서드가 없다면 true를 반환하고, 해당 메서드는 mutex의 소유권을 갖게 된다. mutex를 가진 메서드는 mutex가 관리하는 리소스에 접근할 수 있다.

 

해당 메서드가 종료되거나 리소스에 대한 접근을 완료하였다면 ReleaseMutex()를 호출하여 mutex의 소유권을 해제한다. mutex 해제는 오직 mutex 소유권을 가진 메서드만이 할 수 있다.

 

 


 

스레드 간 동기화

mutex를 사용해서 숫자출력을 동기화하는 코드를 작성해보았다.

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

namespace TestThread
{
    public class Func
    {
        int count = 1;

        Mutex mut = new Mutex();

        public void PrintNum(object name)
        {
            mut.WaitOne();
            Thread.CurrentThread.Name = name.ToString();
            Console.WriteLine($"{Thread.CurrentThread.Name} has entered the protected area");
            for (int i = count; i < count + 10; i++)
            {
                Console.WriteLine($"count {i}!");
                Thread.Sleep(100);
            }
            count += 10;
            Console.WriteLine($"{Thread.CurrentThread.Name} is leaving the protected area");
            mut.ReleaseMutex();
        }
    }

    internal class Program
    {
        static void Main(string[] args)
        {
            Func f = new Func();
            new Thread(f.PrintNum).Start("first thread");
            new Thread(f.PrintNum).Start("second thread");
        }
    }
}

 

 

결과는 다음과 같이 나온다. 상황에 따라서는 first thread 이름의 스레드가 먼저 점유할 수 있다.

 


 

프로세스 간 동기화

프로세스간 동기화는 mutex를 생성할 때 이름을 부여해주면 된다. 일반적으로 mutex명은 GUID(Globally Unique IDentifier)를 사용한다.

 

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

namespace TestThread
{
    public class Func
    {
        int count = 1;

        Mutex mut = new Mutex();

        public void PrintNum(object name)
        {
            mut.WaitOne();
            Thread.CurrentThread.Name = name.ToString();
            Console.WriteLine($"{Thread.CurrentThread.Name} has entered the protected area");
            for (int i = count; i < count + 10; i++)
            {
                Console.WriteLine($"count {i}!");
                Thread.Sleep(100);
            }
            count += 10;
            Console.WriteLine($"{Thread.CurrentThread.Name} is leaving the protected area");
            mut.ReleaseMutex();
        }
    }

    public class App
    {
        public Action onComplete;
        public void Start()
        {
            Console.WriteLine("App start");
            Func f = new Func();
            Thread th1 = new Thread(f.PrintNum);
            Thread th2 = new Thread(f.PrintNum);
            th1.Start("first thread");
            th2.Start("second thread");

            th1.Join();
            th2.Join();
            this.onComplete();
        }
    }

    internal class Program
    {
        static void Main(string[] args)
        {
            bool initiallyOwned = true;
            string mutexName = "abcedsfafeifm;cosdkNFio;SEHfoiEWh";     //아무렇게나 막 썩내린 이름
            bool createdNew;

            Mutex mtx = new Mutex(initiallyOwned, mutexName, out createdNew);
            if (createdNew)
            {
                mtx.WaitOne();
                App app = new App();
                app.onComplete = () =>
                {
                    Console.WriteLine("finish");
                    //mtx.ReleaseMutex();
                };
                app.Start();
            }
            else
            {
                Console.WriteLine("현재 프로세스에 접근할 수 없습니다.");
            }
        }
    }
}

 

 

메인에 생성자를 사용하여 mutex를 생성한다.

new Mutex(Boolean, String, Boolean)으로 호출한다.

첫번째 인자: 호출한 스레드가 뮤텍스의 초기 소유권을 가져야 할지

두번째 인자: 뮤텍스 이름 부여

세번째 인자: 뮤텍스의 소유권이 부여되었는지(이미 실행중인 뮤텍스가 있다면 false 반환, 그렇지 않다면 true 반환)

 

뮤텍스의 소유권이 아직 부여되지 않았다면 뮤텍스를 부여한 뒤, app 클래스를 가동시킨다.

 

프로세스를 중복으로 실행하려 할 때 한쪽에서 차단된 모습을 확인할 수 있다

 


 

참고사이트