Как я могу предотвратить чередование двух операций друг с другом при одновременном разрешении одновременного выполнения?

1

У меня есть два метода: foo() и bar(). Будет много потоков, которые вызовут эти методы, возможно, в одно и то же время. Это потенциально проблематично, если foo() и bar() запускаются одновременно, поскольку чередование их внутренней логики может оставить систему в противоречивом состоянии. Тем не менее, это совершенно нормально, и на самом деле желательно, чтобы несколько потоков могли одновременно вызвать foo(), а также для нескольких потоков, чтобы иметь возможность вызывать bar() одновременно. Конечным условием является то, что ожидается, что foo() вернется как можно скорее, тогда как на bar() не будет жесткого ограничения времени.

Я рассматриваю различные способы, с помощью которых было бы лучше контролировать это поведение. Использование synchronized в его простейшей форме не работает, потому что это будет блокировать одновременные вызовы для каждого метода. Сначала я подумал, что ReadWriteLock может быть подходящим, но это позволит только один из методов вызывать одновременно с самим собой. Еще одна возможность, которую я рассматривал, заключалась в очередности запросов на эти методы в двух отдельных очередях и наличии потребителя, который будет одновременно выполнять каждый foo() в очереди, а затем каждый bar() в очереди, но это похоже на то, что было бы сложно настройте так, чтобы избежать ненужной блокировки foo().

Какие-либо предложения?

  • 0
    Есть ли шанс сделать ваш объект, содержащий методы foo и bar , неизменным?
  • 0
    Вероятно, не поможет - внутренняя логика в этом случае включает в себя как удаленный вызов API, так и доступ к базе данных.
Теги:
multithreading
concurrency

2 ответа

1
Лучший ответ

Я считаю, что хорошим решением будет создание отдельного класса, контролирующего доступ к каждому из методов. Вы должны создать одноэлемент этого класса, а затем использовать его для управления, когда будет нормально продолжать вводить любой метод.

Это третья итерация. Это предотвращает голод.

Использование может быть внешним для вызова foo():

em.enterFoo(Thread.currentThread());
foo();
em.exitFoo();

но, вероятно, будет более чистым, чем вызовы при входе и выходе из foo(), если это возможно.

Код:

public static class EntryManager
{
    private int inFoo = 0;
    private int inBar = 0;
    private Queue<Thread> queue = new LinkedList<>();

    public synchronized void enterBar(Thread t) throws InterruptedException
    {
        // Place the Thread on the queue
        queue.add(t);

        while(queue.peek() != t)
        {
            // Wait until the passed Thread is at the head of the queue.
            this.wait();
        }

        while(inFoo > 0)
        {
            // Wait until there is no one in foo().
            this.wait();
        }
        // There is no one in foo.  So this thread can enter bar.
        // Remove the thread from the queue.
        queue.remove();         
        inBar++;    
        // Wakeup everyone.
        this.notifyAll();
    }

    public synchronized void enterFoo(Thread t) throws InterruptedException
    {
        // Place the thread on the queue
        queue.add(t);

        while(queue.peek() != t)
        {
            // Wait until the passed Thread is at the head of the queue.
            this.wait();
        }

        while(inBar > 0)
        {
            this.wait();
        }
        // There is no one in bar.  So this thread can enter foo.
        // Remove the thread from the queue.
        queue.remove(); 
        inFoo++;
        // Wakeup everyone.
        this.notifyAll();
    }

    public synchronized void exitBar()
    {
        inBar--;
        // Wakeup everyone.
        this.notifyAll();
    }

    public synchronized void exitFoo()
    {
        inFoo--;
        // Wakeup everyone.
        this.notifyAll();
    }
}
  • 0
    Ясно и сжато. Но я думаю, что должна быть добавлена операция ожидания для доступа к foo или bar.
  • 0
    Это справедливая концепция, но, как написано, она не предотвратит голодание - постоянный поток одной операции не позволит потокам, пытающимся выполнить другую операцию, когда-либо завершиться.
1

Я не знаю названия для этой проблемы, поэтому я бы написал свой собственный вспомогательный объект синхронизации, чтобы справиться с этим. Это звучит очень похоже на блокировку чтения/записи, за исключением того, что если блокировка чтения/записи допускает любое количество читателей одновременно или ровно один писатель, но не тот и другой; ваша блокировка позволит любое количество foo() или любое количество баров(), но не обоих.

Трудная часть будет гарантировать, что замок будет честным. Нет проблем, если нет конкуренции, но что, если блокировка находится в режиме "foo", и есть устойчивый поток потоков, которые хотят вызвать foo(), и только один или два, которые хотят вызвать bar(). Как работают потоки bar()?

На самом деле, это напоминает мне много светофора на пересеченной местности. Светофор может позволить автомобилю течь по восточному/западному маршруту или по северному/южному маршруту, но не к обоим. Вы не хотите, чтобы свет переключался слишком часто и просто пропускал одну или две машины за каждый цикл, потому что это было бы неэффективно. Но вы также не хотите, чтобы свет заставил водителей так долго ждать, что они разозлились.

У меня возникло ощущение, что эта политика может быть специально разработана для вашего конкретного приложения. Т.е., это может зависеть от того, как часто вызываются две функции, вызываются ли они в пакетах и т.д.

Я бы начал с исходного кода блокировки чтения/записи и попытался взломать его, пока он не сработает для меня.

  • 0
    Это то, что я, вероятно, в конечном итоге буду делать (переписывая ReadWriteLock), но я хотел посмотреть, распознает ли кто-нибудь это как решенную проблему в первую очередь.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню