J'ai le code suivant :
class TimeOutException
{};
template <typename T>
class MultiThreadedBuffer
{
public:
MultiThreadedBuffer()
{
InitializeCriticalSection(&m_csBuffer);
m_evtDataAvail = CreateEvent(NULL, TRUE, FALSE, NULL);
}
~MultiThreadedBuffer()
{
CloseHandle(m_evtDataAvail);
DeleteCriticalSection(&m_csBuffer);
}
void LockBuffer()
{
EnterCriticalSection(&m_csBuffer);
}
void UnlockBuffer()
{
LeaveCriticalSection(&m_csBuffer);
}
void Add(T val)
{
LockBuffer();
m_buffer.push_back(val);
SetEvent(m_evtDataAvail);
UnlockBuffer();
}
T Get(DWORD timeout)
{
T val;
if (WaitForSingleObject(m_evtDataAvail, timeout) == WAIT_OBJECT_0) {
LockBuffer();
if (!m_buffer.empty()) {
val = m_buffer.front();
m_buffer.pop_front();
}
if (m_buffer.empty()) {
ResetEvent(m_evtDataAvail);
}
UnlockBuffer();
} else {
throw TimeOutException();
}
return val;
}
bool IsDataAvail()
{
return (WaitForSingleObject(m_evtDataAvail, 0) == WAIT_OBJECT_0);
}
std::list<T> m_buffer;
CRITICAL_SECTION m_csBuffer;
HANDLE m_evtDataAvail;
};
Les tests unitaires montrent que ce code fonctionne bien lorsqu'il est utilisé sur un seul thread, tant que le constructeur par défaut de T et les opérateurs de copie/affectation ne lancent pas. Puisque j'écris T, c'est acceptable.
Mon problème est la méthode Get. Si aucune donnée n'est disponible (c'est-à-dire que m_evtDataAvail n'est pas défini), quelques threads peuvent bloquer sur l'appel WaitForSingleObject. Lorsque de nouvelles données sont disponibles, ils passent tous par l'appel Lock(). Un seul d'entre eux passera et pourra récupérer les données et passer à autre chose. Après l'appel Unlock(), un autre thread peut passer et constater qu'il n'y a pas de données. Actuellement, il renvoie l'objet par défaut.
Ce que je veux, c'est que ce deuxième thread (et les autres) reviennent à l'appel WaitForSingleObject. Je pourrais ajouter un bloc else qui déverrouille et fait un goto, mais c'est tout simplement diabolique.
Cette solution ajoute également la possibilité d'une boucle sans fin puisque chaque retour redémarre le délai d'attente. Je pourrais ajouter du code pour vérifier l'horloge à l'entrée et ajuster le délai à chaque retour, mais alors cette simple méthode Get devient très compliquée.
Avez-vous des idées sur la façon de résoudre ces problèmes tout en maintenant la testabilité et la simplicité ?
Oh, pour ceux qui se demandent, la fonction IsDataAvail n'existe que pour les tests. Elle ne sera pas utilisée dans le code de production. Les fonctions Add et Get sont les seules méthodes qui seront utilisées dans un environnement non-test.