Study/에러 정리

멀티스레드 환경 서버의 패킷 지연처리

Juzdalua 2024. 9. 7. 13:35

아래는 메인함수의 스레드풀을 작동시키는 부분이다.

할당된 워커 스레드들은 반복문을 돌며 입출력 큐에 등록된 작업들을 비동기로 진행한다.

각 스레드마다 락이 잠겨있어 경합을 억제한다.

// Worker Threads
mutex m;
vector<thread> workers;
for (int32 i = 0; i < MAX_WORKER_COUNT; i++)
{
	workers.push_back(thread([&]()
		{
			while (true) {
				lock_guard<mutex> guard(m);
				service->GetIocpCore()->Dispatch();
			}
		}));
}
cout << "===== Worker Thread Start =====" << endl;

this_thread::sleep_for 함수를 사용한 문제

문제의 함수
{
    ...
    // 1차로 클라이언트에 전송
    GRoom.Broadcast(MakeSendBuffer(pkt, packetId));

    // 2차 로직
    ...
    this_thread::sleep_for(3s); // 3초 대기 후 전송.

    // 2차 로직 전송
    GRoom.Broadcast(MakeSendBuffer(createRoomPkt, createRoomPacketId));

    return true;
}

1차로 패킷을 클라이언트에 전송하고 2차로직 결과를 전송하기 전, 해당 스레드는 3초간 잠이 든다.

이 때 다른 스레드들이 클라이언트의 패킷을 처리할 거라고 생각했다.

하지만 3초동안 클라이언트에서 받는 패킷들을 처리하지 않고 계속 대기하는 현상이 발생했다.

락을 걸고 I/O 큐에 진입한 스레드가 해당 큐에서 빠지지 않고 대기 상태에 처해 발생한 문제이다.

시간이 길어지면 데드락으로 이어질 수도 있는 문제였다.

 

비동기 스레드 작업으로 해결

문제의 함수
{
	...
    // 1차로 클라이언트에 전송
	GRoom.Broadcast(MakeSendBuffer(pkt, packetId));

	// 2차 로직
	...
	std::thread([=]() {
		std::this_thread::sleep_for(std::chrono::seconds(3));
		GRoom.Broadcast(MakeSendBuffer(createRoomPkt, createRoomPacketId));
		}).detach();

	return true;
}

해당 작업을 처리하던 스레드는 detach()구문을 만나면 별도의 스레드에게 해당 작업을 비동기로 할당한다.

독립적으로 할당된 스레드는 독립적으로 해당 구문을 마치고 종료된다.

 

메인함수에서 CPU가 기용 가능한 모든 스레드들을 워커로 등록하면 어떻게 될지 궁금했다.

GPT의 말로는 작업을 하지 않고 대기하던 스레드가 투입된다고 한다.

 

 

이번 에러는 직접 에러라고 표기되지는 않았지만, 내부적으로 데드락을 유발시킬 수 있는 심각한 에러였다고 생각한다.

어떤 유튜브에서 this_thread::sleep_for 함수를 남발하는 개발자는 무책임한 개발자라고 했던걸 본 기억이 있는데 조심해야겠다.