Home 13강 SpinLock 구현
Post
Cancel

13강 SpinLock 구현

13강 SpinLock 구현

⭐⭐⭐

면접에서 자주 나옴 (구현 해보았는지)

⇒ 뺑뺑이 (무한대기)를 하면서 컴파일 앤 스왑 하는 방법

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
using System;
using System.Threading;
using System.Threading.Tasks;

namespace ServerCore
{
    //lock을 획득할 때까지 무한정 대기하는 데드락 해결 방법 (문제 있는 코드 방식)
    class SpinLock
    {
        volatile bool locked = false;

        public void Acquire() {

            while (locked)
            {
                //lock이 풀리기를 대기함
            }

            //lock이 풀렸다면
            locked = true;
        }

        public void Release()
        {
            locked = false;
        }

    }

    class Program
    {
        static int num = 0; //코드들이 원자적으로 실행이 되는지 판단하기 위한 변수. 의도한 값: 0이 나와야 함
        static SpinLock myLock = new SpinLock(); //스핀락 클래스 객체 myLock

        static void Thread_1()
        {
            for (int i = 0; i < 1000000; i++)
            {
                myLock.Acquire(); //lock을 얻을 때까지 무한정 대기
                num++;
                myLock.Release();               
            }
        }

        static void Thread_2()
        {
            for (int i = 0; i < 1000000; i++)
            {
                myLock.Acquire(); //lock을 얻을 때까지 무한정 대기
                num--;
                myLock.Release();
            }
        }

        static void Main(string[] args)
        {
            //실행하기
            Task t1 = new Task(Thread_1);
            Task t2 = new Task(Thread_2);
            t1.Start();
            t2.Start();
            Task.WaitAll(t1, t2); //대기가 취소되지 않는 경우 제공된 모든 Task 개체가 실행을 완료하기를 기다립니다.

            //결과 출력
            Console.WriteLine(num); // 0이 나와야 코드들이 원자적으로 실행된 것임
            //======
            //결과  : 이상한 값 1766
        }

    }
}

⇒ 0이 나와야 하지만

이상한 값이 나옴

  • 두 스레드가 동시에 락을 가져가려고 하게 때문에 오류가 발생함
  • 코드가 원자적으로 실행이 되어야 함

문제 부분

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    //lock을 획득할 때까지 무한정 대기하는 데드락 해결 방법 (문제 있는 코드 방식)
    class SpinLock
    {
        volatile bool locked = false;

        public void Acquire() {

            while (locked)
            {
                //lock이 풀리기를 대기함
            }

            //lock이 풀렸다면
            locked = true;
        }

        public void Release()
        {
            locked = false;
        }

    }
  • while문 때문에 계속 대기하게 된다
    • ⇒ 해결 : Interlocked 함수

    • 해결 버전 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    //lock을 획득할 때까지 무한정 대기하는 데드락 해결 방법 (올바른 스핀락 구현 법)
    class SpinLock
    {
        volatile int locked = 0; //Exchange 위해 타입을 바꿔줌

        public void Acquire() {

            while (true)
            {
                //[1] 버전-1
                int origin = Interlocked.Exchange(ref locked, 1);
//원자 단위 연산으로 단정밀도 부동 소수점 숫자를 지정된 값으로 설정하고 원래 값을 반환합니다.
                // 값을 1로 exchange == lock을 잠근다
                if (origin == 0) break;
//origin 값이 0일때 == lock이 풀려있는 상태였을 때, lock을 얻었으므로 while 반복문 탈출

            }

        }
  • 해결 버전 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    //lock을 획득할 때까지 무한정 대기하는 데드락 해결 방법 (올바른 스핀락 구현 법)
    class SpinLock
    {
        volatile int locked = 0; //Exchange 위해 타입을 바꿔줌

        public void Acquire() {

            while (true)
            {
                //[2] 버전-2 (더 가독성 좋은 코드)
                int expectedValue = 0; //의도한 num의 값
                int desired = 1;
                //CompareExchange : locked 값이 expectedValue값과 같다면 desired 값으로 바꿔준다
                // == locked 값이 0 일 때 1을 대입해준다
                if (Interlocked.CompareExchange(ref locked, desired, expectedValue) == expectedValue)
                {
                    break;
                }
            }

        }

코드 정리

스핀락 - 문제 있는 코드 구현

(이상한 값 출력)

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
using System;
using System.Threading;
using System.Threading.Tasks;

namespace ServerCore
{
    //lock을 획득할 때까지 무한정 대기하는 데드락 해결 방법 (문제 있는 코드 방식)
    class SpinLock
    {
        volatile bool locked = false;

        public void Acquire() {

            while (locked)
            {
                //lock이 풀리기를 대기함
            }

            //lock이 풀렸다면
            locked = true;
        }

        public void Release()
        {
            locked = false;
        }

    }

    class Program
    {
        static int num = 0; //코드들이 원자적으로 실행이 되는지 판단하기 위한 변수. 의도한 값: 0이 나와야 함
        static SpinLock myLock = new SpinLock(); //스핀락 클래스 객체 myLock

        static void Thread_1()
        {
            for (int i = 0; i < 1000000; i++)
            {
                myLock.Acquire(); //lock을 얻을 때까지 무한정 대기
                num++;
                myLock.Release();               
            }
        }

        static void Thread_2()
        {
            for (int i = 0; i < 1000000; i++)
            {
                myLock.Acquire(); //lock을 얻을 때까지 무한정 대기
                num--;
                myLock.Release();
            }
        }

        static void Main(string[] args)
        {
            //실행하기
            Task t1 = new Task(Thread_1);
            Task t2 = new Task(Thread_2);
            t1.Start();
            t2.Start();
            Task.WaitAll(t1, t2); //대기가 취소되지 않는 경우 제공된 모든 Task 개체가 실행을 완료하기를 기다립니다.

            //결과 출력
            Console.WriteLine(num); // 0이 나와야 코드들이 원자적으로 실행된 것임
            //======
            //결과  : 이상한 값 1766
        }

    }
}

스핀락 - 올바른 해결

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
using System;
using System.Threading;
using System.Threading.Tasks;

namespace ServerCore
{
    //lock을 획득할 때까지 무한정 대기하는 데드락 해결 방법 (올바른 스핀락 구현 법)
    class SpinLock
    {
        volatile int locked = 0; //Exchange 위해 타입을 바꿔줌

        public void Acquire() {

            while (true)
            {
/*                //[1] 버전-1
                int origin = Interlocked.Exchange(ref locked, 1); //원자 단위 연산으로 단정밀도 부동 소수점 숫자를 지정된 값으로 설정하고 원래 값을 반환합니다.
                // 값을 1로 exchange == lock을 잠근다
                if (origin == 0) break; //origin 값이 0일때 == lock이 풀려있는 상태였을 때, lock을 얻었으므로 while 반복문 탈출
*/

                //[2] 버전-2 (더 가독성 좋은 코드)
                int expectedValue = 0; //의도한 num의 값
                int desired = 1;
                //CompareExchange : locked 값이 expectedValue값과 같다면 desired 값으로 바꿔준다
                // == locked 값이 0 일 때 1을 대입해준다
                if (Interlocked.CompareExchange(ref locked, desired, expectedValue) == expectedValue)
                {
                    break;
                }
            }

        }

        public void Release()
        {
            locked = 0;
        }

    }

    class Program
    {
        static int num = 0; //코드들이 원자적으로 실행이 되는지 판단하기 위한 변수. 의도한 값: 0이 나와야 함
        static SpinLock myLock = new SpinLock(); //스핀락 클래스 객체 myLock

        static void Thread_1()
        {
            for (int i = 0; i < 1000000; i++)
            {
                myLock.Acquire(); //lock을 얻을 때까지 무한정 대기
                num++;
                myLock.Release();               
            }
        }

        static void Thread_2()
        {
            for (int i = 0; i < 1000000; i++)
            {
                myLock.Acquire(); //lock을 얻을 때까지 무한정 대기
                num--;
                myLock.Release();
            }
        }

        static void Main(string[] args)
        {
            //실행하기
            Task t1 = new Task(Thread_1);
            Task t2 = new Task(Thread_2);
            t1.Start();
            t2.Start();
            Task.WaitAll(t1, t2); //대기가 취소되지 않는 경우 제공된 모든 Task 개체가 실행을 완료하기를 기다립니다.

            //결과 출력
            Console.WriteLine(num); // 0이 나와야 코드들이 원자적으로 실행된 것임
            //======
            //결과  : 올바른 값 0
        }

    }
}
  • 알아야 할 점 : stack 에 있는 변수 값은 경합이 일어나지 않는다 !

  • interlocked.exchange 를 쓰는 이유 :

    • 코드가 여러줄에 걸쳐 있으면 코드가 스레드에 나뉘어 돌면서 문제가 생김

    int origin = Interlocked.Exchange(ref locked, 1); 아래의 코드를 한줄로 나타낸 것임

    1
    2
    
      int origin = locked;
      locked = 1;
    

    .

This post is licensed under CC BY 4.0 by the author.