포스트

락(Lock)과 데드락(DeadLock)

이번 포스팅은 락Lock과 데드락DeadLock에 관하여 알아보겠습니다. 먼저 간단히 비유를 통해 이해해보겠습니다.

Lock들어와서 문을 잠가주고 나갈때 다시 열어놓는 행위.
DeadLock나갈때 문을 안열어놓고 가서 다음 들어올 사람이 못들어오는 현상.(창문으로 몰래 나감)

Lock은 이미 우리가 지난 포스팅(경합조건과 원자성)에서 실습을 해보았습니다. 쪼개어지지 않도록 원자성이 확보된 코드 뭉치를 원할 때 Interlocked.Increment() 메소드를 사용했었습니다. 이번에는 Increment(), Decrement()외에 어떠한 내용의 코드라도 원자성을 확보하게 해주는 방법을 알아보겠습니다. 예시는 지난 포스팅에서와 같이 number++, number– 쓰레드 예시입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
using System;
using System.Data;
using System.Threading;

namespace ServerCore
{

    internal class Program
    {
        static int number = 0;
        static object _obj = new object();

        static void Thread_1()
        {
            for (int i &#x3D; 0; i < 1000000; i++)
            {
                number++;
            }
        }

        static void Thread_2()
        {
            for (int i &#x3D; 0; i < 1000000; i++)
            {
                number--;
            }
        }

        static void Main(string[] args)
        {
            Task t1 &#x3D; new Task(Thread_1);
            Task t2 &#x3D; new Task(Thread_2);
            t1.Start();
            t2.Start();

            Task.WaitAll(t1, t2);

            Console.WriteLine(number);
        }
    }
}

분명 똑같은 횟수만큼 더하고 빼줬는데 뺀 횟수가 더 많은 것처럼 출력이 되었습니다. 이제 코드의 원자성을 확보해줍시다. 사용할 방법은 두 가지입니다. 하나는 Monitor메소드를 사용하는 방법,

1
2
3
4
static object _obj &#x3D; new object();
Monitor.Enter(_obj);
// 원하는 코드 뭉치
Monitor.Exit(_obj);

다른 하나는 lock(){ }를 사용하는 방법입니다.

1
2
3
4
5
static object _obj &#x3D; new object();
lock (_obj)
    {
     // 원하는 코드 뭉치
    }

각각 사용해봅시다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
        static object _obj &#x3D; new object();
        static void Thread_1()
        {
            for (int i &#x3D; 0; i < 1000000; i++)
            {
                // 상호배제 Mutual Exclusive
                Monitor.Enter(_obj); //화장실 들어와서 문을 잠그는 행위
                number++;
                Monitor.Exit(_obj);  //잠금을 풀고 화장실에서 나간다.
            }
        }

        static void Thread_2()
        {
            for (int i &#x3D; 0; i < 1000000; i++)
            {
                Monitor.Enter(_obj);
                number--;
                Monitor.Exit(_obj);
            }
        }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
        static object _obj &#x3D; new object();
        static void Thread_1()
        {
            for (int i &#x3D; 0; i < 1000000; i++)
            {
                // 상호배제 Mutual Exclusive
                lock (_obj)
                {
                    number++;
                }
            }
        }

        static void Thread_2()
        {
            for (int i &#x3D; 0; i < 1000000; i++)
            {
                lock (_obj)
                {
                    number--;
                }
            }
        }

두 쓰레드 연산이 짝이 맞추어져 잘 실행되었습니다.


그럼 이제 데드락DeadLock을 일으켜봅시다. Monitor.Exit() 만 빼주면 됩니다.

누군가 방에 들어가기는 했는데 나오질 않아서 다음 순번의 사람이 못들어가고 하염없이 기다리고만 있는 상황입니다. 실수로 Exit()을 빼주거나, 코드 상에서 Exit()의 수행이 안되도록 로직을 짠다면 데드락이 쉽게 일어날 수 있습니다. 그래서 데드락을 방지하기 위해서는 더 사용이 간편한 lock()을 사용하는 게 좋겠습니다.

Monitor().Enter()와 .Exit()으로 들어오고 나감.만약 .Exit()을 빼먹고 실행하지 못한다면 다음 순번이 .Enter()를 못하게 되는 위험(데드락)이 있음.
lock()중괄호{ }로 열고 닫아주므로 데드락을 일으키는 실수를 줄일 수 있음.

이 글은 인프런의 '[C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버' 강의를 참고하여 정리 목적으로 작성하였습니다. 감사합니다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.