Проблема с использованием семафора для защиты очереди

Я использую следующий код для ограничения использования ресурсов.

Время от времени (после 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() и обновлена ​​ссылка на правильный набор изменений.

Другие вопросы по тегам