Существует lock
, который предоставляет эксклюзивный доступ к разделу или ресурсу для всех. И есть ReaderWriterLock(Slim)
, который предоставляет общий доступ всем читателям и эксклюзивный доступ каждому писателю.
То, что я ищу, - это метод синхронизации, чтобы разрешить общий доступ к группе A и общий доступ к группе B, но не к любым A и B одновременно. С точки зрения блокировки чтения / записи, это может быть произвольное число читателей или произвольное количество писателей, но одновременно нет читателя и писателя.
Другим способом объяснить это может быть дорожная развязка со светофорами . Только одна сторона имеет зеленый цвет. Каждый с этой стороны может пройти перекресток. Как только кто-то встает в очередь на другой стороне, зеленый становится красным. Никто не может войти в соединение больше. И только после того, как все вышли из перекрестка, следующая ожидающая сторона становится зеленой, и все с этой стороны могут пройти.
Есть ли подходящее решение для этого в .NET Framework? Или какие части я бы использовал для этого?
Семафоры ограничивают количество пользователей на одном ресурсе, но не обеспечивают координацию нескольких ресурсов. Я не знаю других используемых классов прямо сейчас.
Для чего это хорошо?
Я работаю в системе PubSub (публикация / подписка) с возможностью доставки прошлых опубликованных сообщений во время подписки. Операции подписки и отмены подписки могут выполняться параллельно, они обновляют внутренние регистрационные данные, а операции подписки также считываются из буфера сообщений, чтобы определить, какие прошлые сообщения отправлять новому подписчику. Операции публикации читают только регистрационные данные, но добавляют новые элементы в буфер сообщений и отправляют их подписчикам. Операции sub или pub могут выполняться параллельно, но не оба типа. Это необходимо для предотвращения отправки опубликованных сообщений дважды (во время подпрограммы и затем паба), а также для предотвращения потери сообщений (между подпрограммой и пабом).
Пример кода
Я пытался ее решить, но не смог найти решение. Вот мой текущий черновой код:
using System;
using System.Threading;
public class MultiStateLock
{
// The current confirmed state
private int currentState;
// The state somebody is waiting to enter
private int awaitedState;
// The number of callers in the current state
private int enteredCount;
// Enters a state. Blocks until the state is switched.
public void Wait(int state)
{
// Is the current state not what we want to enter?
Thread.MemoryBarrier();
if (currentState != state)
{
var spinWait = new SpinWait();
// Is some other state currently being awaited? If not, await the new state
while (Interlocked.CompareExchange(ref awaitedState, state, 0) != 0 && awaitedState != state)
{
spinWait.SpinOnce();
}
// Is somebody else still entered in another state?
// TODO: These two variables need some synchronisation, without blocking Leave()
Thread.MemoryBarrier();
while (currentState != state && enteredCount > 0)
{
spinWait.SpinOnce();
Thread.MemoryBarrier();
}
// Switch the current state
currentState = state;
// Let others await another state
awaitedState = 0;
}
// Say we're in the state
// TODO: This may only be done if currentState wasn't already changed again
Interlocked.Increment(ref enteredCount);
}
// Leaves the entered state.
public void Leave()
{
Thread.MemoryBarrier();
if (enteredCount <= 0)
throw new InvalidOperationException("Cannot leave the state that wasn't entered.");
// NOTE: Maybe the state to be left could be verified, too.
// Or maybe this check could be removed altogether if it's not reliable.
// They shouldn't be needed in correct implementations.
// Say we're out of the state. Were we the last in this state?
if (Interlocked.Decrement(ref enteredCount) == 0)
{
// Let others switch to their awaited state
// TODO: This may only be done if enteredCount wasn't already incremented again
// (and effectively currentState hasn't changed)
currentState = 0;
}
}
}
Предполагается, что столько абонентов будут в одном состоянии, сколько пожелают. Как только любой абонент захочет войти в другое состояние, все новые абоненты должны будут ждать. Когда все вызывающие абоненты покинули свое состояние (так что никто не находится в каком-либо состоянии в течение краткого момента), первый ожидающий может войти в свое состояние, и все, кто ждал того же состояния, могут следовать за ним.
Я даже не осмелился попробовать это. Я знаю, что в нем отсутствуют синхронизации, просто продумав это. Смотрите комментарии TODO и NOTE. Я мог бы просто поставить lock
вокруг Enter
и Leave
, но, поскольку Enter
будет ждать, он будет блокировать любые вызовы на Leave
в ожидании запуска Leave
. Это будет тупик.
Я намеренно не использую volatile
поля, поскольку они могут быть неправильно синхронизированы , и я не до конца пойму это.