인프런 커뮤니티 질문&답변

literate_t님의 프로필 이미지
literate_t

작성한 질문수

[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버

Lock-Free Stack #2

Lock-Free Stack #2 01:16쯤에 말씀하신 동시에 TryPop() 할 경우..질문입니다.

작성

·

62

·

수정됨

0

안녕하세요!

동시에 TryPop() 을 할 경우, oldHead를 참조하는 스레드가 있을 수 있고 이 때 무작정 해제해버리면 use after delete 오류가 발생할 수 있다고 하셨습니다.

TryDelete() 함수를 구현하실 때, 1 == popCount 안에서 delete old_head 를 할 때는 이미 분리했으니까 괜찮다고 하셨는데요. 첫 문단에서 언급한 부분과 반대되는 말을 하신 것 같다는 느낌을 받았습니다.

제가 생각했을 때는, 저희 수업 때 사용하신 예제 코드에서는, cas 연산을 통해 oldHead 를 분리했기 때문에 use after delete 오류가 안 날 것 같단 생각이 들더라고요.

궁금해서 테스트를 해봤습니다.
TryDelete() 함수를 호출하지 않고 바로 delete old_head; 를 호출하는 코드를 넣었고요, 지금 이 질문을 올리고 있는 와중에 push()sleep_for(1ms)를 주고 pop()을 하는 스레드는 5개를 돌려서, 진단 도구에서 확인되는 프로세스 메모리가 1mb이 유지되게 설정해서 30분 넘게 돌려보고 있습니다. 오류가 안 나고 있는 광경을 목격 중입니다. 그냥 운이 좋은 걸까요?

오류가 발생하지 않는 이유는 다음과 같다 생각합니다.

atomic 변수인 head에서 cas 연산을 하기 때문에 간발의 차이로 먼저 다음 코드를 통과한 스레드가 획득한 oldHead는 다른 스레드가 이후에 될 oldHead와 같지 않다. while 조건에서 true를 반환하는 순간 headheadnext를 가리키고 있게 되기 때문이다. compare_exchange_weak() 함수가 true를 반환하면 head는 원자적으로 head->next를 가리키는 상태가 된다.

while (old && false == _head.compare_exchange_weak(old, old->next))
      return false;

 

고견 부탁드립니다.

멋진 강의 감사합니다.

답변 1

1

안녕하세요, 인프런 AI 인턴입니다. literate_t님.

질문해주신 내용을 통해 Lock-Free Stack #2의 TryPop 함수에서 oldHead를 삭제하는 상황에 대해 여러 가지 의문을 갖고 계신 것을 알 수 있었습니다. 관련해서 유사한 질문과 답변들이 여러 있어서 도움이 될 것 같습니다.

  1. 아토믹 관련 질문입니다!

    • 이 질문에서 CAS 연산과 _head 변경을 다루며 atomic 연산이 어떻게 작동하는지에 대해 상세히 설명하고 있습니다.
    • 특히 CAS 연산이 atomic하게 작동하여 _head.compare_exchange_weak가 실패하면 _head가 업데이트되지 않아서 크래시가 발생하지 않는 이유를 잘 설명합니다.
  2. TryDelete 함수 질문 드립니다

    • 여기서도 oldHead의 사용과 삭제에 대해 자세히 설명하고 있으며, _popCount를 사용하여 어떻게 thread-safe한 방법으로 노드를 제거하는지 설명합니다.
  3. 질문있습니다

    • compare_exchange_weak 함수에서 null pointer 참조 문제와 관련된 내용을 다룹니다.
    • atomic 연산의 작동 원리와 CAS의 동작에 대해 이해하는 데 도움이 될 수 있습니다.
  4. Lock-Free Stack #3 문의 있습니다..

    • 여기도 Lock-Free Stack에 대한 다른 질문이 포함되어 있으며, atomic 연산을 어떻게 사용하는지에 대해 더 많은 인사이트를 제공할 수 있습니다.

위의 답변들로 기본적인 개념과 동작 방식을 이해하실 수 있을 겁니다. 추가적으로 요약해드리자면, CAS (compare-and-swap) 연산은 atomic하게 작동하기 때문에, 여러 스레드가 동시에 접근해도 oldHead가 갱신되는 과정을 안전하게 관리합니다. 따라서, while 루프를 통해 CAS 연산이 실패할 때까지 반복하며 head가 업데이트되는 과정을 보장합니다. 이러한 과정 덕분에 동시에 여러 스레드가 TryPop를 호출해도 use-after-delete 문제가 발생하지 않게 됩니다.

질문자님께서 실행한 테스트에서 오류가 나지 않은 이유도 바로 이 때문입니다. CAS 연산을 통해 다음 스레드가 참조할 oldHead는 항상 유효한 값을 가리키고 있으며, 먼저 head를 갱신한 스레드는 _head.compare_exchange_weak가 성공하여 oldHead를 올바르게 분리해 내고 삭제할 수 있습니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

literate_t님의 프로필 이미지
literate_t
질문자

인프런 인턴 친구의 답변이 틀리지 않았다면 수업 예제에서는 TryPop()을 구현할 때 분리된 oldHead를 바로 지워도 된다는 의미가 되네요.

루키스님 고견이 궁금합니다.

literate_t님의 프로필 이미지
literate_t

작성한 질문수

질문하기