작성
·
437
1
0. 비트연산 (<<)을 하고나서 &연산을 하는 이유가, 이미 다른 값이 있을 때를 방지한다고 하셨는데, 쓰레기 값을 의미하는 것인가요?
1. 재귀 Lock에서 같은 쓰레드가 들어왔을 때만 lockCount가 올라가는 이유는 뭔지 궁금합니다. WriteLock이 잡혀 있을 땐 어짜피 다른 쓰레드는 기다리게되기 때문인가요? 그렇다면 예를 들어서 1 쓰레드가 writeLock을 하고 있는 상황이라면 1 쓰레드만 재귀적인 접근이 가능하다는 의미인가요?
어.. 음 예를 들어서,
1 frame) Tread1.WriteLock Tread2.WriteLock
- Thread1이 Lock을 걸음
- Tread2 대기 상태
2 frame) Tread1.WriteLock
- Tread1.WriteCount ++;
- Tread2 대기 상태
3 frame) Tread1.WriteUnLock -> WirteCount --;
- Tread2 대기 상태
예시와 같이 쓰레드의 WirteLock요청 타이밍에 따라 WirteLock의 순서가 보장되지 않는 상황이 일반적인 것인지, 아니면 고급 로직은 이런 순서 또한 보장되는지 궁금합니다.
2. 예제에서 보여주신 재귀 Lock은, 어떤 상황에서 이점이 있나요? 혹여나, 같은 쓰레드가 WriteLock() 요청을 여러번 했을 때(?)와 같은 문제 상황에서 좀 더 가시적라 문제 해결이 쉬워서일까요? 만약 그렇다면, 재귀적 상황을 고려하지 않은 예제 코드를 사용한다고 했을 때, WriteLock코드에서 비트연산으로 나온 desired가 같은 쓰레드일 경우 return 처리를 해야 할까요?
답변 2
1
0.
다른 값을 방지하는 것은 맞는데,
[쓰레기]까지는 아니고 실제 원하는 값만 추출하기 위함입니다.
예제에서 [1][15(writeThreadId)][16(readCount)]로 비트를 쪼개서 사용하고 있는데,
특정 마스크를 적용하면 원하는 부분을 걸러낼 수가 있습니다.
예를 들어 WriteLock 코드에서
WRITE_MASK와 &을 해서 [16] 부분을 0으로 쫙 밀어버리고 있는데,
해당 부분이 readCount와 관련되어 Write과는 관련 없는 정보이기 때문입니다.
물론 WriteLock 경우 ManagedThreadId가 어마어마하게 큰 값을 갖지는 않으니
엄밀히 말하면 불필요할 수도 있겠지만,
그냥 null 체크를 하듯 안전을 위함이라고 이해해주시면 되겠습니다.
유사하게 flag & READ_MASK를 하면 [16]에 해당하는 부분만 남기고 나머지를 0으로 밀어버리는데,
이 값을 expected에 저장하고 있습니다.
이 경우에는 정말 특별한 의미를 갖게 되는데,
만약 [15(writeThreadId)] 부분이 0이 아니라면,
CompareExchange 함수가 무조건 실패하게 될테니,
간접적으로 if (writeThreadId != 0)를 체크했다고 생각하시면 되겠습니다.
1.
이해하신 것이 맞습니다.
writeCount을 관리하는 이유는,
말 그대로 WriteLock을 중첩해서 걸 수 있게 하기 위함입니다.
그리고 이렇게 writeCount를 늘릴 수 있는 쓰레드는,
이미 락을 잡고 있는 쓰레드만 유일하게 가능하니 ThreadId 비교를 해준 것이죠.
나머지 쓰레드들은 락이 풀리길 하염없이 기다리고 있어야 합니다.
추가로 예제로 올려주신 예제에서 frame이 무엇을 의미하시는건지 잘 이해가 안 가네요.
서버의 경우 클라와 같은 '프레임' 개념이 딱히 없고
모두 실시간으로 연산을 한다 보시면 됩니다.
2.
Lock에서 재귀 호출을 허용할 것인지 정책이 갈리게 되는데,
참고로 C++ 표준 std::mutex는 안 되고, Windows C++ CriticalSection은 됩니다.
재귀 호출이 가능하냐 여부가 은근 중요한 이유는 단순히 코딩 편의성과 실수 방지 차원입니다.
나중에 서버 코드가 방대해지면 함수끼리 서로 호출하는 상황이 빈번해지는데,
혹시라도 실수해서 Lock을 거는 함수에서 또 Lock을 거는 함수를 호출하면
바로 무한 루프 or 크래시가 일어나기 때문입니다.
재귀 호출이 허용되지 않으면 보통 함수 이름에 ~~MovePlayerLocked()와 같이
락을 거는지 아닌지 여부를 이용해서 구별을 해서,
~Locked() 함수에선 또 ~Locked()을 호출하지 않도록 주의를 해야 합니다.
마지막으로 재귀 여부와 desired는 아무런 상관이 없습니다.
왜냐하면 코드 흐름상 flag == EMPTY_FLAG일 경우에만 desired로 바꿔치기 하는 것이고,
이 부분은 writeCount와는 전혀 연관성이 없기 때문입니다.
(아무도 락을 걸고 있지 않을 때) 라는 조건은
WriteThreadID가 0이 아닌지가 중요하지,
Write를 잡고 있는 쓰레드의 writeCount가 1, 2, 3, 4인지는 전혀 상관하지 않습니다.
따라서 재귀 호출이 필요 없으면 if (Thread 비교) writeCount++ return; 부분만 제거해주시면 됩니다.
RW락은 딱히 이 프로젝트에선 사용하지 않을거고,
그냥 멀티쓰레드 교양(?) 차원에서 다룬 내용이니
정확히 이해 안 가셔도 딱히 상관은 없습니다.
저도 처음에 이해하는데 오래 걸렸던 기억이 나네요.
0
답변 감사합니다!!!
야밤에 보니 정신이나가서 아리쏭 하던 부분을 제 맘대로 끼워 마추려했던 것 같습니다.
확실히, 다시 강의를 보니 read 쪽 CompareExchange 제대로 이해하지 못했었네요..
덕분에 확실히 이해했습니다. 다시한번 감사드려요!