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;
.