지난 시간에 이어서 이제 Lock을 구현을 해볼 것인데
그래서 먼저 SpinLock부터 알아 볼 것인데
참고로 SpinLock의 경우 면접에서도 굉장히 자주 나온다.
면접의 0번째 질문으로 멀티쓰레드를 구현을 할 때, SpinLock을 구현을 해보았냐?? 고 물어 보기 때문에 주의 깊게 보아라
그래서 코드로 이것을 구현을 해볼 것인데
그래서 보면 이전에 배웠던 Moniter.Enter, Moniter.Exit 이런식으로 구현이 될 것이다.
그래서 그것을 보통
이런식의 이름으로 만든다.
그리고 상태를 확인 해주기위해서
이렇게 만들어 주자.
그리고 Acquire 함수안에
_locked가 true이면 뺑뺑이를 돌도록 만들어주자.
왜냐하면 지금 _locked가 true인 경우는 다른 녀석이 먼저와서 사용을 하고 있다는 뜻이니까
while로 계속 안에 대기를 하도록 만들어 주는 것이다.
그리고 Release에서도 이제는 다 사용을 했으니 _locked를 false로 바꿔주면
이렇게 SpinLock을 어느정도 구현을 한거 같다..!
그래서 이것을 잘 돌아가는지 테스트를 함 해보면은
이전에 했던것과 같은 예제로 쓰레드를 이렇게 만들어서
num을 ++ || -- 해주는 작업을 할것인데
그전에 먼저
Acquire로 lock을 먼저 잡은 다음에
소유권을 얻었으면 num++ 헤주고
늘렸다면 이제 Release()를 통해 소유권을 풀어 주도록 하자.
Thread_2도 이런식일 것이다.
그리고 이런식으로 쓰레드를 만들어서 실행을 해서 테스트를 해보면되는데
우리가 만든 SpinLock이 제대로 구현이 되어있다면
0이 나올 것이다.
실행을 해보면
0은 커녕 이상한 값이 나오는데
이게 지금
왜이러냐면..!
화장실에 들어간다.
문을 잠군다.
이런식으로 행동을 두가지로 나누어서 실행을 하고 있기 때문에 그런것이다.
그러니까 진짜 동시에 둘이 들어 갈 수가 있는 부분인데
지금은 명령을 한게 "화장실 안에 들어가라"라는 명령만 했기 때문에
(들어 오는데만 초점이 맞춰 져있기 때문에)
누가 같이 들어가는 것에 신경을 쓰지않고
막 둘이서 비집고 들어간다음 옆에 누가 있는지도 모르는 상황에서 문을 잠구고
각자 자신의 할 일을 한 것이다.
그래서
화장실에 들어간다.
문을 잠군다(자기 할일 한다)
화장실에 들어가고 들어간 다음에 문을 잠구겠다라고 하면 안된다.
이것을 두가지의 행동으로 나누어서 실행하게 할 것이 아니라
한번에 실행하도록 해주어야한다!
이게 이전에 배웠던 "원자성" 과도 같은 문제인 것이다.
그래서 멀티 쓰레드 환경에서 뭔가의 행동이 원자적으로 일어 나야하는것은 그래서 중요한 개념이 되겠고
그래서 아까의 문제를 해결을 하려면은
애초에 화장실에 동시에들어가는 그런 상황을 차단을 해야 할 것이다.
그래서 코드를 다시 보자면은
정확하게 이 부분이 문제가 되고 있던 것이다.
이렇게 while(_locked) 는 화장실문이 비어 있을 때 까지 계속 기다리는 부분이 될 것이고
아래의 _locked = true는 화장실에 아무도 없으니 내가 쓰겠다! 라는 부분이 된다.
그런데 이것을 지금 따로따로 하고있으니까
문제가 되는 것이다.
그래서 이부분을 동시에 실행이 되도록 만들어 주어야 한다는 것이다.
우리가 이전에 number++을 할때도 이런 비슷한 상황이 있었는데
그것을 해결을 할때 사용하던 것이 바로
InterLock 계열의 함수를 사용을 하는 것이였다.
그래서 이번에도 InterLock 계열의 함수를 사용을 할 것인데
그래서 이렇게 인터락. 을찍어보면 우리를 도와줄 함수들이 있는데
이중에서 가장 보편적으로 많이 사용을 하는것이 일단,
CompareExchange이고,
그다음 조금 덜 사용하는 것이 Exchange인데
둘다 알아보도록 하겠다
먼저 Exchange부터 알아보자.
그래서
Excahnge를 보면 location1 이라는 것이 있고, value값이 있는데
지금 < T > Generic으로 받고잇는중이다.
그래서 boolean을 넣어 줘도 될거같은 생각이 드는데 T를 넣어줄 때 조건이 있다.
where T : class 라는 조건때문에 ( 참조 형식이여야 된다는 말 )이니까
boolean을 쓸 수 없다는 뜻이 되고
그래서 우리는 int형 버젼을 사용을 할 것이다.
이버젼을 사용 할 것이다.
( 참고로 c++에서는 boolean 버젼도 다 마련이 되어있다.)
그래서 이것이 어떻게 동작하나면 레퍼런스(ref)로 location1을 넣어 주게 될 것이고
그다음 늘려줄 값(더해줄 값) value를 넣게 된다.
그런데 Exchange가 뱉어 주는 값은 original값인데 (value를 넣어주기 전의 값)
이것을 int original로 받아서
값이 변경이 안되었다면 누군가가 먼저와서 잠군 상태가 아니라고 판단을 할 수 있을 것이다.
그래서 이렇게 잠군다고 하면은 Exchange(_locked, 1);이렇게 셋팅을 해야 할 것이다.
그런데 사실은 이렇게 무조건 1을 대입을 한다는 것은
_locked = 1;
그냥 이렇게 대입하는 것이랑 별반 다를 바가 없으니가
Exchange가 뱉어주는 original값을 이용할 것이다.
그래서
if (original == 0)
였다고 하면은
아무도 없었다는 얘기가 되는 것이고
if (original == 1)
라는 상태라는 것은 우리가 어거지로 문을 다시 잠군 상태가 될 것이다.
if (original == 0)
그래서 이 상태가 우리가 원하는 이상적인 상태가 되는 것이다.
아무도 오지 않은 상태에서 내가 문을 잠구었다! 라는 상태이다.
그래서 이 상태를 만족을 할 때까지 뺑뺑이를 돌면되고
만족을 한다면 break로 탈출을 하면 될 것이다.
그래서 정확히 이 상태가 되어야 한다.
그래서 이렇게하고 실행을 해보면
정확히 0이 나오는 것을 볼 수 있다.
그래서 이렇게해서 매우매우 간단한 방법으로 구현을 해보았다.
그리고 유의해야 할 부분이
이런식으로 _locked는 경합해서 == 공유해서 사용하는 녀석이기 때문에
이런식으로 이 녀석을 마음대로 읽어와서 사용을 하면 안된다고 했었는데
그럼에도 불구하고
뱉어준 값(original)을 그대로 사용을 해서 if문으로 비교를 하고 있었는데
그것은 왜 가능 한 것이냐면
original은 stack에 있는, 즉, 경합하지 않는
하나의 쓰레드에서만 사용을 하고 있는 녀석이기 때문에 이런식으로 사용을 해도 아무런 문제가 없다.
( 참고로 static도 안붙어 있다 )
그래서 이제
멀티쓰레드 프로그래밍을 한다면
Redzone, blueZone을 구별할 어느부분이 위험하고 유의해야 되는지
구별을 할 수 있는 매의 눈이 필요하다.
지금 같은 경우에는 _locked가 진짜 조심해서 다뤄야 하니까 조심조심해서 다뤄야 되고
이런 stack에 들어가있는 녀석은 이전에 사용하던 것처럼 사용하면 될 것이다.
그래서 Exchange 부분을 의사코드로 표현을 하자면
이런 느낌인데
위에보다는 아래 부분이 조금더 깔끔할 거 같은 느낌이 드는데
이게 CompareExchange이다.
그래서 대부분의 경우에는
CompareExchange를 사용을 한다 왜냐하면 조금더 범용적으로 사용을 할 수 있기 때문이다.
그래서 이 버젼을 알아 볼 것이다.
이전과 별반 다를 것은 없는데, 첫인자는 우리가 조작하기를 원하는 값을 넣어 줄 것이고
이렇게
그다음 두번째 세번째 인자는
이 함수가 하는 역할은
location1과 comparand와 비교를 해가지고 두개가 같다라고 하면은
두번째 인자로 넣어준 value의 값을 location(첫번째 인자의 값)에 넣어주게 될 것이다.
그리고 뱉어주는 값은 아까와 마찬가지로 original값을 뱉어 주게 될 것이다.
그리고 이런 계열의 함수를
Compare-And-Swap 계열의 함수라고 하는데
당연히 C++에도 마련이 되어있다.
다만 인터페이스가 많이 다르다.
그러니까 함수에 인자를
이렇게 넣어주는게 언어마다 순서와 그런 것들이 조금씩 달라기지고 햇갈리는데
우리가 하고 싶었던 작업이 _locked가 0이라면 1로 바꿔주는게 우리가 하고 싶었던 작업이니까
이렇게 넣어 주면 될 것이다.
그런데 이렇게까지만하면 성공했는지 실패했는지 알 수 없으니까 original 값을 가져와서 비교를 해주면된다.
그래서 이렇게 하면된다.
그리고 참고로 c++에서는 int를 뱉어 주는 것이 아니라 boolean을 뱉어 준다 ( 성공했는지 실패를 했는지를 )
언어마다 인터페이스가 다르다고 했는데
햇갈리기 때문에
(c++ 에서는 location1, value, caparand)가 아니라 expected, desire 이런식으로 되어있기 때문에
이런식으로 함 해보자.
그래서 이렇게하면 읽기가 조금더 수월해 진거같다.
그래서 내가 예상한 값과 desired값을 넣어주어서 expected한 값이 나오면은
location1에다가 desired 을 넣어주겠다! 라는 말이 된다.
그래서 이런식으로 수정을 조금 읽기 편하게 해줄 수 있을 것이다.
그래서 이런식으로 나오는 것을 볼수 있다. 잘된다!
그리고 유의해야할게
문을 열어주는 작업은 별도의 처리를 할 필요 없이 그냥 0을 넣어주기만 하면된다.
왜냐하면
이곳을 통과했다는 얘기는 유일하게 내가 _locked를 물고 있다는 얘기 이니까
그런것이다.
그래서 나중에 면접이나 그런데서
굉장히 중요
스핀락을 구현을 해보셨나요? 라고 물어보면
Compare-And_Swap 연산을 해서 구현하는 lock이다 라고 당당하게 대답하면된다!