Written

메모리 배리어 본문

Server/멀티스레드

메모리 배리어

steeringhead 2023. 7. 18. 15:03

 

싱글 스레드, 멀티 스레드 코드 흐름의 차이

 

 

 

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
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
 
namespace client
{
    // 메모리 배리어
    // 1) 코드 재배치 억제
    // 2) 가시성
    class Program
    {
        static int x = 0;
        static int y = 0;
        static int r1 = 0;
        static int r2 = 0;
 
        static void Thread1()
        {
            
            y = 1//Sotre y
            r1 = x; //Load x
            Console.WriteLine("스레드 1");
        }
        static void Thread2()
        {
            
            x = 1;
            r2 = y;
            Console.WriteLine("스레드 2");
        }
 
        static void Main(string[] args)
        {
            int count = 0;
            while (true)
            {
                count++;
 
                x = y = r1 = r2 = 0;
 
                Task t1 = new Task(Thread1);
                Task t2 = new Task(Thread2);
                t1.Start();
                t2.Start();
 
                Task.WaitAll(t1, t2);
 
                if (r1== 0 && r2 == 0)
                {
                    break;
                }
                
            }
            Console.WriteLine($"{count}번 만에 빠져나왔습니다 ! ");
        }
    }
}
cs

 

멀티 스레드 환경에서는 코드의 흐름에 주의하자 !

 

=> 우리가 싱글 스레드에서 해왔던대로 생각해보면, 위 코드에서 r1 과 r2가 0이되면서 break를 통해 빠져나오는 것은 불가능하다. 그런데 어떻게 빠져나올 수 있었을까? 게다가 count의 값은 실행할 때 마다 바뀐다.

 

=> 일단 t1, t2로 두개의 스레드를 생성했는데, 두 스레드 중 무엇이 먼저 실행될지는 예측할 수 없다. 실제로 콘솔에 출력을 해보면 알수 있다. 스레드1이 먼저 실행될 때도 있고, 스레드2가 먼저 실행되는 경우도 있다. 그것이 멀티 스레드의 특징.

 

=> 심지어 y = 1 다음줄에 r1 = x를 하도록 코드를 구현했는데, 실행할 때 두 코드가 서로 독립적이면서 둘의 순서를 바꿔서 실행하는게 더 빠르다고 내부에서 판단하면, 우리가 구현한 순서대로 실행하는게 아니라 둘의 순서를 바꿔서 실행할 수 있다는 놀라운 사실이 존재한다. 그리고 하나의 스레드가 다른 스레드의 실행을 기다려주거나 하지 않기 때문에, r1 = 0 이면서 r2 = 0 이 참이되어 나올 수 있었던 것.

 

=> 그러면 우리가 구현한 순서대로 실행하게끔 하려면 어떻게 해야할까? Thread.MemoryBarrier(); 의 도움을 받으면 가능하다. Thread.MemoryBarrier()는 임의로 코드가 재배치되는 것을 억제하면서 또 하나 중요한 역할을 하는데 그것은 다른 스레드에게 나의 변동사항을 업데이트하고, 다른 스레드의 변동사항을 읽는 것!   ->  이게 무슨뜻이냐면 여러개의 스레드는 각자 스레드마다 고유의 스택영역이 있고, 힙 영역의 데이터들은 스레드끼리 공유하는데 누군가 공유하는 영역의 메모리의 변수의 값을 변경했다면 다른 스레드들이 그 내용을 갱신해야 의도치 않은 오류를 방지할 수 있다는 것!

 

=> Thread.MemoryBarrier()를 사용한다 하더라도 여전히 어떤 스레드가 먼저 실행될지는 모르기 때문에, 그 점을 잘 고려하여 코드를 구현해야한다. 예를 들면 아래의 코드를보면, 어떤 스레드가 먼저실행됬느냐에 따라 _complete의 값이 다르게 출력된다.

 

 

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
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
 
namespace client
{
    // 메모리 배리어
    // 1) 코드 재배치 억제
    // 2) 가시성
    class Program
    {
        static int _answer = 0;
        static bool _complete = false;
        static void A()
        {
            _answer = 123
            Thread.MemoryBarrier();
 
            _complete = true;
            Thread.MemoryBarrier();
        }
 
        static void B()
        {
            Thread.MemoryBarrier();
            if (_complete)
            {
                Thread.MemoryBarrier();
                Console.WriteLine(_answer);
            }
            else
            {
                Console.WriteLine($"complete is false");
            }
        }
 
        static void Main(string[] args)
        {
            int count = 0;
            while (true)
            {
                count++;
 
                _answer = 0;
                _complete = false;
 
                Task t1 = new Task(A);
                Task t2 = new Task(B);
 
                t2.Start();
                t1.Start();
 
                Task.WaitAll(t1, t2);
 
                Console.WriteLine($"{count} 바퀴");
                            
                
            }           
            
        }
    }
}
cs

 

 

 

 

'Server > 멀티스레드' 카테고리의 다른 글

SpinLock  (0) 2023.07.21
DeadLock  (0) 2023.07.21
캐시(Cache) 이론  (0) 2023.07.18
Comments