(멀티 쓰레드 통신) Conditional Variable의 wait 함수의 대기상태에서 다시 활성화되지 않는 문제
멀티 쓰레드 통신 프로그램에서 Conditional Variable의 wait 함수를 실행해서 대기 상태인 쓰레드가
해당 조건이 만족되었는데도 불구하고, 다시 활성화되지 않는 문제가 발생합니다.
이러한 문제가 항상 발생하는 것은 아니고 가끔씩 발생되어, 프로그램이 대기 상태로 더 이상 진행되지 않습니다.
고수분들의 많은 조언 부탁드립니다.
본 프로그램은 2개의 노드가 연산된 데이터를 송수신하면서 실행하는 프로그램입니다.
각 노드는에서는 5개의 쓰레드가 실행되며 그 역할은 다음과 같습니다.
1. 메인 쓰레드
2. 송신 쓰레드 : 데이터 송신을 담당하는 쓰레드
3. 수신 쓰레드 : 데이터 수신을 담당하는 쓰레드
4. 연산 쓰레드(2개) : 데이터의 연산을 담당하고 연산 효율성을 위해서 멀티 쓰레드로 작동함.
문제가 발생하는 데이터 수신 부분의 자세한 설명은 다음과 같습니다.
Step A) 데이터를 수신하려는 연산 쓰레드는 Received Queue에 연산쓰레드의 ID(2 바이트)에 해당하는 데이터 들어올때까지 대기합니다.
ulRecv.lock(); RecvCV->wait(ulRecv, [&] { RecvPtr = RecvQueue->front(); return !RecvQueue->empty() && RecvPtr[0]==(idx&0xff) && RecvPtr[1]==(idx>>8); });
Step B) 수신 쓰레드에서 데이터를 수신하면, (Mutex의 lock실행) Received Queue에 데이터를 Push하고 (Mutex의 unlock실행) notify를 실행합니다.
try { SRecvSocket->recv(RecvBuf, Len); } catch ( SocketException& ) {} RecvMtx->lock(); RecvQueue->push(RecvBuf); RecvMtx->unlock(); RecvCV->notify_all();
Step C) 수신 쓰레드의 notify_all 함수에 의해서, Received Queue에 해당 연산쓰레드의 ID에 해당하는 데이터가 있음을 확인하게 되면, 연산 쓰레드는 대기 상태에서 해제되고 해당 데이터를 Pop해서 연산을 수행하게 됩니다.
RecvPtr = RecvQueue->front(); RecvQueue->pop(); ulRecv.unlock();
문제는 (항상은 아니고 가끔씩) step B에서 step C과정으로 넘어가지 않는다는 것입니다.
데이터 로그를 통해서 step A와 step B의 과정이 모두 실행되었음을 확인하였는데,
대기 상태인 연산 쓰레드가 가끔씩 다시 활성화가 되지 않습니다.
그런데, 연산 쓰레드를 하나의 쓰레드(전체 4개의 쓰레드)로 실행할 경우에는 이러한 문제가 발생하지 않습니다.
어떻게 해결해야 할지 감이 안잡히네요.
고수 분들의 조언 부탁드립니다.
감사합니다.
producer consumer problem
Step C 를 notify_all 로 깨우지 마시고, 계속 queue 가 empty 인지를 계속 확인하도록 변경하세요. while loop 이나 timer 등을 사용하면 좋을것 같습니다. 그러면 notify 할 일이 없습니다.
그리고, Step C 가 queue 에서 element 를 pop 한 후 해당 element 에 해당하는 process 를 위해서 1회성의 new thread 를 시작하면 될듯 합니다. 문제는 새로운 thread 가 끝나기 전에 또 다른 thread 가 실행될 수 있습니다. 이것은 queue size 를 제어하듯 thread 의 수를 제어하여 pool 를 관리하는 것으로 해결해야 합니다. 도움이 되었기를 바랍니다.
...
> Step C 를 notify_all 로 깨우지 마시고, 계속 queue 가 empty 인지를 계속 확인하도록 변경하세요. while loop 이나 timer 등을 사용하면 좋을것 같습니다.
이건 문제를 푸는 게 아니라 문제 해결을 포기하는 것 아닙니까. -_-
one producer to one consumer
notify all 이라는게 어짜피 sleep 상태에서 깨우실 텐데, 그렇다면 coroutine 을 사용하시면 될것 같네요.
흠..
Conditional Variable은 사용해 본 적 없지만 event의 waitforsingleobject와 메커니즘이 같은
내용으로 보이는데요...
깨울 때 step C로 안남어 가지는 않을 것 같고 notify_all을 하여 쓰레드를 깨운 상태에서 notify_all이
또 실행되어 굳이 깨울 필요 없는 서버를 다시 꺠우면서 일어난 문제가 아닌가 생각됩니다.
step C에서 루프를 돌려서 큐에 있는 내용을 전부 실행시키게 하면 되지 않을까요?
.
서버 -> 쓰레드 ^^;;;
ulRecv 이 유니크락인거 같은데, RecvMtx
ulRecv 이 유니크락인거 같은데, RecvMtx 뮤텍스를 건드리도록 생성된 것이 맞나요?
A 단계에서는 데이터가 있는지만 체크하고,
C 단계에서 원하는 데이터가 있는지 체크하는 것으로 바꿔야 할거 같습니다.
현상태의 코드에서는, wait 에서 원하고 있는 데이터가 아닌 다른 데이터가 들어온 경우,
데이터가 소모되지(pop) 않으며, 계속 대기 상태로 들어가게 됩니다.
댓글 달기