https://leetcode.com/problems/fizz-buzz-multithreaded/
Приведенная ниже реализация кажется мне логически безопасной, но в LeetCode она выдает разные выходные данные при n = 50. Локально мои тесты не воспроизводят проблему последовательно.
Мой предполагаемый инвариант:
- только поток, чей идентификатор соответствует ходу, может продолжить работу
- все остальные потоки блокируются на своем семафоре
- после печати активный поток освобождает ровно следующий ожидаемый поток thread
- конкретное чередование, которое нарушает этот инвариант, или
- объяснение того, почему сам инвариант ложный
Одним из ограничений дизайна, которые я выбрал намеренно, было вычисление следующего действия только один раз за шаг вывода, а не повторная оценка делимости независимо в каждом рабочем процессе.
Основной метод предположительно создает 4 потока, которые вызывают один раз один из общедоступных методов.
class FizzBuzz {
private final Semaphore sFizz = new Semaphore(0, false);
private final Semaphore sBuzz = new Semaphore(0, false);
private final Semaphore sFizzBuzz = new Semaphore(0, false);
private final Semaphore sNumber = new Semaphore(0, false);
private volatile int index = 1;
private final int max;
private volatile Id turn;
private final Object mutex = new Object();
public FizzBuzz(int n) {
this.max = n;
turn = calculateTurn(index);
}
private enum Id {
FIZZ, BUZZ, FIZZBUZZ, NUMBER
}
private Integer markReady(Id id) throws InterruptedException {
if (id != turn) {
switch (id) {
case NUMBER -> sNumber.acquire();
case BUZZ -> sBuzz.acquire();
case FIZZ -> sFizz.acquire();
case FIZZBUZZ -> sFizzBuzz.acquire();
}
}
synchronized (mutex) {
if (index > max) {
return null;
}
int turnIndex = index++;
if (index sFizzBuzz.release();
case FIZZ -> sFizz.release();
case BUZZ -> sBuzz.release();
case NUMBER -> sNumber.release();
}
}
}
// printFizz.run() outputs "fizz".
public void fizz(Runnable printFizz) throws InterruptedException {
while (index