Проблема с использованием семафора для защиты очереди
Я использую следующий код для ограничения использования ресурсов.
Время от времени (после 3-4 дней успешного запуска) я получаю исключение из очереди или возвращаемый объект оказывается равным нулю.
Мне интересно, если я ограничиваю только 5 потоков для входа в этот метод Get, как это происходит.
В тех местах, где вызывается GetConnection, ReleaseConnection также определенно вызывается в блоке finally.
С каждым звонком я тоже логинюсь нет. ресурсов в очереди. Число очередей никогда не превышает 5.
Semaphore smphSync = new Semaphore(0, 5);
Queue<IResource> resources;
private IResource GetResource()
{
smphSync.WaitOne();
IResource res = resources.Dequeue();
return res;
}
private ReleaseResource(IResource res)
{
resources.Enqueue(res);
smphSync.Release();
}
У меня вопрос: нужно ли синхронизировать доступ к очереди (экземпляру ресурсов) с помощью блокировки / монитора?
2 ответа
По умолчанию ни одна из стандартных коллекций.NET не поддерживает потоки. К ним нельзя получить доступ одновременно без какого-либо барьера памяти, препятствующего одновременному доступу.
В вашем случае семафор предотвращает доступ к более чем пяти потокам resources
но ничто не мешает ни одному из этих пяти параллельных потоков войти Dequeue()
или же Enqueue()
в то же время Вполне возможно, что среди этих потоков возникает редкое состояние гонки, которое приводит к повреждению очереди. Вы должны действительно поставить замок вокруг resources
сама очередь
Я бы также посоветовал вам выполнить тест внутри блокировки, чтобы убедиться, что в очереди все еще есть элементы, которые нужно удалить, прежде чем пытаться вызвать Dequeue()
, Однако, поскольку я не знаю специфики работы вашего кода, я оставляю вам решать, уместно ли это.
Semaphore smphSync = new Semaphore(0, 5);
Queue<IResource> resources;
private _lockObj = new object();
private IResource GetResource()
{
smphSync.WaitOne();
lock( _lockObj )
{
IResource res = resources.Dequeue();
return res;
}
}
private ReleaseResource(IResource res)
{
lock( _lockObj )
{
resources.Enqueue(res);
}
smphSync.Release();
}
Я добавил lock() вокруг моего класса ThreadSafeQueue и недавно добавил метод TryDequeue(). Подробнее в этом посте. Определенно улучшенные многопоточные коллизии, которые я часто видел раньше (особенно возвращение нулевого объекта, когда в очереди не было нулей).
Редактировать: проверено в методе TryDequeue() и обновлена ссылка на правильный набор изменений.