C#

[C#]잠금 및 Lock 키워드

DevStory 2022. 6. 5.

멀티스레딩에서 공유 리소스 접근

멀티스레드로 구현된 애플리케이션에서 여러 개의 스레드 객체가 공유 리소스를 접근하는 것은 중요한 작업입니다. 예를 들어 세 개의 스레드 객체가 존재하고 abcd.txt 파일을 접근한다고 가정합니다.

 

Thread1은 abcd.txt 파일에 데이터를 작성합니다.(Write)

Thread2는 abcd.txt 파일의 데이터를 읽습니다.(Read)

Thread3는 abcd.txt 파일을 제거합니다.(Delete)

 

세 개의 스레드 객체가 동일한 시간에 abcd.txt 파일을 접근하여 각자의 작업을 수행하면, Thread1이 abcd.txt 파일에 데이터를 작성하는 도중 Thread3가 abcd.txt 파일을 제거했으므로 애플리케이션이 제대로 동작하지 않을 수 있습니다.

 

이러한 문제가 발생하지 않도록 C#에서는 스레드 동기화를 위해 다양한 클래스를 제공합니다. 이번 포스팅에서 소개할 lock 키워드를 사용하면 하나의 스레드 객체만 리소스를 접근할 수 있으므로 스레드를 안전하게 처리할 수 있습니다.


멀티스레드의 문제점

다음 예제를 통해 멀티스레드에서 스레드 동기화 처리를 하지 않았을 경우 발생하는 문제를 알아봅시다.

 

세 개의 스레드 객체는 동일한 리소스를 접근하며, 동일한 리소스는 PrintName() 메서드입니다. 소스코드에서 스레드 동기화 작업을 하지 않았으므로 세 개의 스레드 객체는 동시에 PrintName() 메서드를 호출할 수 있습니다.

class Program
{
  public static void PrintName(Object name)
  {
    Console.Write("My Name is ");
    Thread.Sleep(1000);
    Console.WriteLine(name);
  }

  static void Main(string[] args)
  {
    Thread thread1 = new Thread(PrintName);
    Thread thread2 = new Thread(PrintName);
    Thread thread3 = new Thread(PrintName);

    thread1.Start("Thread1");
    thread2.Start("Thread2");
    thread3.Start("Thread3");
  }
}

실행 결과

My Name is My Name is My Name is Thread2
Thread1
Thread3

실행 결과를 보시다시피 원하는 결과가 출력되지 않았습니다. 따라서 멀티스레드로 구현된 애플리케이션에서 공유 리소스를 보호하지 않으면 예기치 않은 문제가 발생할 수 있습니다.


잠금(Lock)

위 예제에서 원하는 결과를 얻기 위해서는 잠금(Lock)이라는 메커니즘을 사용합니다. 잠금은 특정 스레드 객체가 PrintName() 메서드를 호출하고 있으면, 다른 스레드 객체는 PrintName() 메서드에 접근할 수 없도록 하는 메커니즘입니다.

 

따라서 잠금을 사용하면 공유 리소스를 보호할 수 있습니다.

 

잠금은 공유 리소스를 접근할 수 있는 스레드 개수에 따라 배타적 잠금(Exclusive Lock)과 비배타적 잠금(Non-Exclusive Lock)으로 분류됩니다.


배타적 잠금(Exclusive Lock)

배타적 잠금은 공유 리소스를 오직 하나의 스레드만 접근할 수 있습니다. C#에서 지원하는 lock 키워드, Monitor, Mutex, SpinLock 클래스를 사용하여 배타적 잠금을 구현할 수 있습니다.


비배타적 잠금(Non-Exclusive Lock)

비배타적 잠금은 공유 리소스를 접근할 수 있는 스레드의 개수를 제한합니다. C#에서 지원하는 Semaphore, SemphoreSlim, ReaderWriterLockSlim 클래스를 사용하여 비배타적 잠금을 구현할 수 있습니다.


lock 키워드

다음은 C#에서 lock 키워드를 사용하는 방법입니다.

lock (lockObject)
{
  // 로직 작성...
}

lockObject는 잠금을 설정하는 객체입니다. 공유 리소스를 접근하여 특정 작업을 수행하는 로직을 lock 키워드의 코드 블록에 작성합니다.

 

여러 개의 스레드 객체가 공유 리소스를 접근해야 하는 경우 한 개의 스레드 객체만 lock 키워드의 코드 블록에 접근할 수 있습니다. 나머지 스레드 객체는 lock 키워드의 코드 블록이 끝날 때까지 기다려야 하며, lock 키워드의 코드 블록이 끝나면 다른 스레드 객체가 lock 키워드의 코드 블록에 접근합니다.


lock 키워드 예제

다음 예제는 lock 키워드를 사용하여 공유 리소스를 보호하는 방법을 보여줍니다. 잠금을 설정하는 객체를 생성하고 PirntName() 메서드에 lock 키워드를 사용합니다. 따라서 여러 개의 스레드 객체가 PrintName() 메서드를 호출하더라도 공유 리소스가 보호됩니다.

class Program
{
  static object lockObject = new object();
  public static void PrintName(Object name)
  {
    lock (lockObject)
    {
      Console.Write("My Name is ");
      Thread.Sleep(1000);
      Console.WriteLine(name);
    }
  }

  static void Main(string[] args)
  {
    Thread thread1 = new Thread(PrintName);
    Thread thread2 = new Thread(PrintName);
    Thread thread3 = new Thread(PrintName);

    thread1.Start("Thread1");
    thread2.Start("Thread2");
    thread3.Start("Thread3");
  }
}

실행 결과

My Name is Thread2
My Name is Thread1
My Name is Thread3

정리

  • 잠금(Lock)은 멀티스레드 환경에서 공유 리소스를 보호하는 방법입니다.
  • 공유 리소스를 접근할 수 있는 스레드 개수에 따라 배타적 잠금(Exclusive Lock)과 비배타적 잠금(Non-Exclusive Lock)으로 분류됩니다.
  • C#에서 lock 키워드를 사용하여 잠금을 구현할 수 있습니다.
  • lock 키워드를 구현하기 위해서는 잠금을 설정하는 객체가 필요합니다.
  • lock 키워드 코드 블록에 공유 리소스를 접근하여 특정 작업을 수행하는 코드를 작성합니다.
반응형

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

[C#]파일 존재 여부 확인  (0) 2022.06.06
[C#]base 키워드  (0) 2022.06.05
[C#]스레드 동기화(Thread Synchronization)  (0) 2022.06.05
[C#]지역 함수(Local Function)  (0) 2022.05.29
[C#]멀티스레드(MultiThread)  (0) 2022.05.28

댓글