Pull to refresh

Comments 7

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

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

UFO just landed and posted this here

Должно быть верно, здесь логика следущая:

// Ждем пока освободится место для нашего потока, то есть счетчик станет больше 0
Atomics.wait(this.counter, 0, 0);
// Получаем значение счетчика
const n = Atomics.load(this.counter, 0);
// Еще раз проверям на то, что n > 0, так как другой поток мог вклиниться между wait и load
if (n > 0) {
   // Пытаемся атомарно уменьшить счетчик, получив его предыдущее значение
  const prev = Atomics.compareExchange(this.counter, 0, n, n - 1);
   // Если prev === n, значит мы успешно уменьшили счетчик, все ок, можно выходить из цикла
  if (prev === n) return;
  // Eсли prev !== n, значит мы не изменили счетчик, надо пробовать снова - идем в начало цикла
}

Спасибо больше за сатью. Написано интересно!

Задам только тупой вопрос: подход применим только и именно для потоков? Я имею в виду, если я создам n промисов, которые будут читать/писать каку-то глоб. переменную, ситуация гонок ведь тоже будет? И Аtomics с семафором это дело порешают?

Спасибо за внимание!

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

Покажу на примере: есть два промиса, один увеличивает счетчик, если он в нуле, другой уменьшает, если он в единице - в итоге ожидаем увидеть 0.

let sharedCounter = 0;

const checkAndChange = () => {
  const isZero = sharedCounter === 0;
  // сюда не проберется никакой другой поток
  if (isZero) {
    sharedCounter += 1;
  } else {
    sharedCounter -= 1;
  }
}

const incrementIfZero = new Promise((resolve) => {
  resolve(checkAndChange());
});

const decrementIfNonZero = new Promise((resolve) => {
  resolve(checkAndChange());
});

Promise.all([incrementIfZero, decrementIfNonZero]).then(() => {
  console.log(`Result: ${sharedCounter}`);
});

Как написал выше, код в функции checkAndChange выполнится атомарно, никто не может поменять переменную sharedCounter в процессе его выполнения, если программа выполняется в одном потоке.

Другая ситуация, если проверка и манипуляции с переменной происходят в теле промиса и затем в then-колбэке.

let sharedCounter = 0;

const incrementIfZero = new Promise((resolve) => {
  resolve(sharedCounter === 0 ? 1 : 0);
}).then((increment) => {
  sharedCounter += increment;
});

const decrementIfNonZero = new Promise((resolve) => {
  resolve(sharedCounter === 1 ? 1 : 0);
}).then((decrement) => {
  sharedCounter -= decrement;
});

Promise.all([incrementIfZero, decrementIfNonZero]).then(() => {
  console.log(`Result: ${sharedCounter}`);
});

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

Спасибо большое за развернутый ответ. Ваша статья побудила меня самого сегодня написать ряд тестов, чтобы наконец-то уже разобраться в этом деле :)

Sign up to leave a comment.

Articles