inflearn logo
강의

講義

知識共有

C++とアンリアルで制作するMMORPGゲーム開発シリーズ Part4:ゲームサーバー

PacketSession

궁금한 점이 있습니다.

499

cckiz153

投稿した質問数 18

4

강의 초반부에서 Session::Send()를 수정해주셨는데

 

{

    WRITE_LOCK;

    _sendQueue.push(sendBuffer);

}

if(_sendRegistered.exchange(true) == false)

    RegisterSend();

 

이렇게 작성하면 안되는 것일까요?

별도로 스택 변수를 사용해야 되는 이유가 있는지 궁금합니다.

멀티스레드 개념이 잘 안잡혀서 번거롭게 사소한 것까지 

여쭤봐서 죄송합니다..

MMORPG windows-server network

回答 2

0

naringcode

분석해 보았을 때 우려하신 문제가 발생하려면 꽤 까다로운 조건을 충족해야 합니다.

문제 상황 1(보낼 SendBuffer가 없는데 WSASend()를 호출)

1. Send가 걸린 상황

2. Thread A : Lock을 걸어 sendQueue에 데이터를 삽입 후 Lock을 품(Send).

3. Thread B : 완료 루틴에 의해 깨어나 sendQueue에 데이터가 있는지 확인하고 RegisterSend 호출(ProcessSend).

4. Thread B : sendQueue에 예약된 데이터를 전송(RegisterSend).

5. Thread C : 전송 결과를 완료 루틴으로 받고 sendRegistered 플래그를 품(ProcessSend).

6. Thread A : 이어서 sendRegistered 플래그를 확인하고 RegisterSend 호출(Send).

7. Thread A : sendQueue를 확인했으나 SendBuffer가 없는 상황이 발생(RegisterSend).

8. Thread A : 보낼 SendBuffer가 없는데 전송을 시도하니 WSAEINVAL(잘못된 인자) 에러가 뜨고 실패함(RegisterSend).

 

이 문제 상황 1을 더 깊게 파고 들면 다음의 연장선에 위치한 문제를 파악할 수 있습니다.

문제 상황 2(데이터를 넣었는데 보내지 않는 상황)

1. Send가 걸린 상황

2. Thread A : Lock을 걸어 sendQueue에 데이터를 삽입 후 Lock을 품(Send).

3. Thread B : 완료 루틴에 의해 깨어나 sendQueue에 데이터가 있는지 확인하고 RegisterSend 호출(ProcessSend).

4. Thread B : sendQueue에 예약된 데이터를 전송(RegisterSend).

5. Thread C : 전송 결과를 완료 루틴으로 받고 sendRegistered 플래그를 품(ProcessSend).

6. Thread A : 이어서 sendRegistered 플래그를 확인하고 RegisterSend 호출(Send).

7. Thread A : sendQueue를 확인했으나 SendBuffer가 없는 상황이 발생(RegisterSend).

8. Thread A : 보낼 SendBuffer가 없는데 전송을 시도하니 에러가 뜨고 실패함(RegisterSend).

int32 errCode = ::WSAGetLastError();
if (WSA_IO_PENDING != errCode)
{
    HandleError(errCode);

    _sendEvent.owner = nullptr;
    _sendEvent.sendBuffers.clear();

    // <-- 여기서 처리가 지연되는 상황이 발생

    _sendRegistered.store(false);
}

9. Thread D : Lock을 걸어 sendQueue에 데이터를 삽입 후 Lock을 품(Send).

10. Thread D : sendRegistered 플래그를 확인했는데 누군가 점유 중인 상태이니 RegisterSend를 호출하지 않음(Send).

11. Thread A : sendRegistered 플래그를 false로 바꿈(RegisterSend).

12. Thread D에서 넣은 SendBuffer를 처리하지 못 하는 상황이 발생.

정확히 말해선 7번 과정에서 sendQueue에 있는 내용을 전부 빼오고 Lock을 푸는 그 시점부터 WSASend() 호출 후 에러가 발생해 sendRegistered를 false로 바꾸기 이전의 모든 구간에서 문제 상황 2가 발생할 수 있습니다.

애초에 WRITE_LOCK을 걸고 sendQueue에 변화를 주는 로직과 sendRegistered의 상태를 전이하는 로직이 분리되어서 생기는 문제이기에 다음과 같이 Lock을 건다고 해서 해결되지 않습니다.

sendQueue는 이미 변화되었고 이를 탐지하지 못 했기에 발생한 문제이니까요.

int32 errCode = ::WSAGetLastError();
if (WSA_IO_PENDING != errCode)
{
    HandleError(errCode);

    _sendEvent.owner = nullptr;
    _sendEvent.sendBuffers.clear();

    WRITE_LOCK;

    _sendRegistered.store(false);
}

ProcessSend()의 문제가 아니라 여러 스레드를 통해 Send() -> RegisterSend()를 하는 과정에서 발생하는 문제입니다.
그리고 그 과정에서 문제 상황 1이 먼저 선행되어 에러가 발생할 수 있는 조건이 충족되어야 합니다.

0

Rookiss

정말 심오한 문제인데...
사실 이건 한 번 깨져봐야 터득할 수 있습니다.
스포일러를 드리자면, 당장은 별 문제가 없어 보이지만
나중에 _sendRegistered를 false로 초기화해주는 시점이 분명 있을겁니다.
그런데 정말 절묘하게 타이밍이 어긋나면

2쓰레드) RegisterSend 실행중
1쓰레드) _sendQueue.push
1쓰레드) if(_sendRegistered.exchange(true) == false)
 에서 exchange 실패해서 통과
2쓰레드) _sendRegistered = false

요런 식으로 어긋나서 아무도 전송을 안 하는 이슈가 생기게 됩니다.
그런데 이런건 겁내지 말고 이것저것 고쳐보면서 문제를 많이 만나보는게 학습할 땐 좋습니다.

0

cckiz153

강사님 답변 정말 감사드립니다.

exchange와 store가 서로 다른 스레드에서 동시에 수행될 때 문제가 발생할 수 있다는 것을 깨달았습니다.

강사님께서 말씀해주신대로

{

    WRITE_LOCK;

    _sendQueue.push(sendBuffer);

}

if(_sendRegistered.exchange(true) == false)

    RegisterSend();

와 같이  WRITE_LOCK을 풀고 RegisterSend를 하게 되면 답변해주신 것처럼 

에코 스트레스 테스트를 해봤을 때 약 10000개의 패킷 중에 1개 정도의 유실이 발생했습니다.

강사님께서 알려주신대로 exchange와 store가 동시에 실행될 수 있는 시점이 있는가에 대해서

이틀동안 고민해봤는데

Send()에서  _sendRegistered.exchange(true)를 수행하기 전에 WRITE_LOCK을 잡고

큐에 푸시한 후 LOCK을 해제하고

ProcessSend()에서는 WRITE_LOCK을 잡고 큐가 비었는지를 검사하고 이때 비어있어야먄

store를 수행하는데 이렇게 되면 exchange와 store가 동시에 수행될 수 있는 경우가 있는건지

아무리 고민해봐도  이해가 되질 않습니다..

 

USE_LOCK은 큐에 대한 동기화 객체이고 _sendRegistered는 WSASend 작업에 대한 동기화 객체라고 이해했는데 제가 잘못 이해한 것일까요? WRITE_LOCK을 잡고 RegisterSend()까지 수행해버리면 사실상 Session Send에 대한 전체 락이니 분리해야 효율적인 것은 알겠는데 왜 강사님께서 알려주신대로

WRITE_LOCK안에서 작업하면 문제가 없는데(스트레스 테스트를 했을 때에 당연히 문제가 발생하지 않았습니다..) 밖에서 sendRegistered를 건드면 안되는건지 이해가 어렵습니다.

 

 

3달 전 처음 이 강의를 봤을 때에도 이 부분이 너무 궁금해서 오랫동안 고민해봤지만 해결이 되지 않았고 강의를 처음 보고 이미 진도가 많이 지나갔지만 아직도 이해가 되지 않는게 왜 강사님 코드처럼

WRITE_LOCK안에서 RegisterSend()를 수행하면 문제가 없는데 밖에서 수행하면 문제가 발생하는지

너무 어렵습니다.

강의를 처음 들었을 때부터 너무 궁금했던 부분이라 염치불구하고 다시 질문드립니다.. ㅠㅠ

 

0

Rookiss

WRITE_LOCK 안에서 RegisterSend()를 하면 문제 없는 이유는
[_sendQueue에다 데이터를 처음으로 밀어넣는 애가무조건 실행도 책임지고 해준다]~라는
법칙이 성립하기 때문입니다.

if(_sendRegistered.exchange(true) == false)
    RegisterSend();

WRITE_LOCK 밖에다 이렇게 해도 얼핏 같아 보이지만,
아주 의미가 달라지고 큐에다 데이터를 넣는 행위와, RegisterSend가 
두 단계로 나눠지게 됩니다.

문제의 상황은 이미 어떤 쓰레드가 RegiterSend를 호출중일 때 발생합니다.
해당 쓰레드가 하는 일은
- 1) sendQueue에 예약된 모든 데이터 전송
- 2) 다 보냈으면 _sendRegistered 플래그를 꺼줌

위 2가지 일을 하는데, 만약 두 일감이 따로 따로 실행된다면 (두 단계로) 멀티쓰레드 이슈가 생깁니다.
왜냐하면 1과 2 사이에 절묘하게 
다른 쓰레드가 개입해서 sendQueue에다 데이터를 넣어줄 수 있기 때문이죠.
아직 2가 실행되진 않았으므로,
데이터를 방금 밀어넣은 쓰레드 측에서 살펴보면
    if(_sendRegistered.exchange(true) == false)
은 만족하지 않아 데이터를 보내려 하지 않습니다.



0

cckiz153

강사님 그런데 저희가 배운 강의코드를 보면 RegisterSend()나 ProcessSend()가 이미 수행 중인 상황에 푸쉬가 되더라도 푸쉬를 하기 위해선 락을 잡아야되고 ProcessSend()에서 큐가 비었는지 검사를 하기 전에도 락을 잡고 검사한 후 대신 보내주기 때문에 문제가 발생할 수 없지 않나요? 

0

Rookiss

_sendRegistered를 풀어주는 부분은 LOCK과 무관하게 별도로 실행되기 때문에
2 단계로 나뉘어지면 아래 그림과 같이, 중간에 틀어짐이 발생할 수 있습니다.
이 부분이 이해가 안가시면 더 고민을 해보세요. (왼쪽 : A쓰레드, 오른쪽: B쓰레드)

Memory Pool에서 오버플로우 질문입니다.

0

64

2

포토폴리오 및 진로 관련하여 고민입니다.

0

112

1

포토폴리오 관련 고민입니다.

0

62

1

실무에서도 alloc, 스마트포인터 등을 구현해서 쓰는지 궁금합니다.

0

82

2

성능 테스트 결과

0

103

2

게임 서버 Stateful, Stateless 진로 고민

0

121

1

WaitOnAddress와 Sleep의 차이 질문

0

83

1

궁금한거 있습니다.

0

82

2

JobTimer 구동 스레드

0

107

2

TryPop() 동작 관련 질문

0

81

1

로드맵 C#서버 C++서버 방향성 질문

0

148

2

스레드 id를 출력할떄 메인스레드 id도 출력되나요?

0

73

1

생명주기를 위한 의도적 복사

0

87

2

락프리의 실무에서 사용 질문

0

141

2

32bit threadID와 16비트 상위 WriteFlag에 대해

0

101

2

mutex와 sleep 차이점

0

118

1

실무에서는 어떠한 코드 스타일을 사용하는지 궁금합니다

0

152

2

Stomp Allocator의 Release함수에 대한 질문입니다.

0

96

1

공부법 관련해서

0

183

2

MakeShared 함수 관련

0

114

1

지금까지 서버코어에서 만든 내용에 대해 궁금한 점이 있어서 질문 드립니다.

0

144

2

운영체제관련 질문입니다

0

131

1

send하려는 데이터 크기가 크면 memcpy에서 문제가 발생할 것 같습니다.

0

117

2

메모리 풀 질문있습니다.

0

124

1