Синхронизация между двумя группами операций - PullRequest
0 голосов
/ 06 мая 2018

Существует 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 поля, поскольку они могут быть неправильно синхронизированы , и я не до конца пойму это.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...