У меня есть следующий код:Требуются: элегантное решение гонки состояние
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;
};
Модульное тестирование показывает, что этот код работает отлично при использовании на одном потоке, пока конструктор T по умолчанию и копировать операторы/присваивания не» т бросить. Поскольку я пишу T, это приемлемо.
Моя проблема - метод Get. Если данных нет (т. Е. M_evtDataAvail не установлен), то несколько потоков могут блокировать вызов WaitForSingleObject. Когда новые данные становятся доступными, все они попадают на вызов Lock(). Только один пройдет и может получить данные и двигаться дальше. После разблокировки() другой поток может перемещаться и обнаруживать, что данных нет. В настоящее время он вернет объект по умолчанию.
Что мне нужно для этого второго потока (и других), чтобы вернуться к вызову WaitForSingleObject. Я мог бы добавить блок else, который был разблокирован и сделал goto, но это просто чувствует зло.
Это решение также добавляет возможность бесконечного цикла, так как каждый откат будет перезапускать таймаут. Я мог бы добавить код для проверки часов на входе и настроить таймаут в каждой поездке назад, но затем этот простой метод Get начинает очень усложняться.
Любые идеи о том, как решить эти проблемы, сохраняя при этом проверяемость и простоту?
О, для кого-то интересно, функция IsDataAvail существует только для тестирования. Он не будет использоваться в производственном коде. Add и Get - это единственные методы, которые будут использоваться в среде без тестирования.
Во-первых, я исторически использовал RAII именно в этих обстоятельствах. В этом случае я решил не увеличивать площадь тестирования. Я просто попадаю в TDD. –
Во-вторых, БРИЛЛИАНТ! Я изменил блок if-empty-reset на if-not-empty-set, и каждый путь, о котором я могу думать, обрабатывается должным образом. Благодаря! –
+1, авторешетка будет делать. Существует один недостаток использования autoReset здесь. Если в m_buffer добавлено несколько элементов, используя метод «Добавить», существует вероятность того, что потоки ждут, но m_buffer все еще не отобрал некоторые элементы. –