Подтвердить что ты не робот

Haskell: Как работает "atomicModifyIORef"?

Может кто-нибудь объяснить, как работает atomicModifyIORef? В частности:

(1) Дождаться блокировки или оптимистично попытаться повторить попытку, если есть конкуренция (например, TVar).
(2) Почему подпись atomicModifyIORef отличается от подписи modifyIORef? В частности, какова эта дополнительная переменная b?

Изменить: Я думаю, что я выяснил ответ на (2), в котором b - это значение, которое нужно извлечь (это может быть пустым, если не нужно). В однопоточной программе знание значения тривиально, но в многопоточной программе, возможно, захочется узнать, каково было предыдущее значение во время применяемой функции. Я предполагаю, что именно поэтому modifyIORef не имеет этого дополнительного возвращаемого значения (так как в этом случае использование modifyIORef с этим возвращаемым значением, вероятно, должно использовать atomicModifyIORef. Я все еще интересуюсь ответом на (1).

4b9b3361

Ответ 1

atomicModifyIORef принимает a r :: IORef a и функцию f :: a -> (a, b) и выполняет следующие действия:

Он считывает значение r и применяет f к этому значению, давая (a',b). Затем r обновляется с новым значением a', а b - возвращаемое значение. Этот доступ для чтения и записи выполняется атомарно.

Конечно, эта атомичность работает только в том случае, если все обращения к r выполняются с помощью atomicModifyIORef. Обратите внимание, что вы можете найти эту информацию, посмотрев на источник [1].

[1] http://hackage.haskell.org/packages/archive/base/latest/doc/html/src/Data-IORef.html#atomicModifyIORef

Ответ 2

Ожидает ли блокировка или оптимистически пытается повторить попытку, если есть конкуренция (например, TVar).

atomicModifyIORef использует инструкцию блокировки на базовой аппаратной архитектуре, на которой вы находитесь, для упорядочивания указателя на выделенный объект Haskell.

В x86 он использует встроенную версию cas, представленную как примитив к языку через atomicModifyMutVar#, который реализован как служба времени выполнения в Cmm как:

stg_atomicModifyMutVarzh
{
...

 retry:
   x = StgMutVar_var(mv);
   StgThunk_payload(z,1) = x;
#ifdef THREADED_RTS
   (h) = foreign "C" cas(mv + SIZEOF_StgHeader + OFFSET_StgMutVar_var, x, y) [];
   if (h != x) { goto retry; }
#else
   StgMutVar_var(mv) = y;
#endif
...
}

То есть, он попытается выполнить обмен и повторить попытку.

Реализация cas как примитива показывает, как мы добираемся до металла:

/*
 * Compare-and-swap.  Atomically does this:
 */
EXTERN_INLINE StgWord cas(StgVolatilePtr p, StgWord o, StgWord n);

/*
 * CMPXCHG - the single-word atomic compare-and-exchange instruction.  Used
 * in the STM implementation.
 */
EXTERN_INLINE StgWord
cas(StgVolatilePtr p, StgWord o, StgWord n)
{
#if i386_HOST_ARCH || x86_64_HOST_ARCH
    __asm__ __volatile__ (
      "lock\ncmpxchg %3,%1"
          :"=a"(o), "=m" (*(volatile unsigned int *)p)
          :"0" (o), "r" (n));
    return o;
#elif arm_HOST_ARCH && defined(arm_HOST_ARCH_PRE_ARMv6)
    StgWord r;
    arm_atomic_spin_lock();
    r  = *p;
    if (r == o) { *p = n; }
    arm_atomic_spin_unlock();
    return r;
#elif !defined(WITHSMP)
    StgWord result;
    result = *p;
    if (result == o) {
        *p = n;
    }
    return result;

Итак, вы можете видеть, что он может использовать атомную инструкцию в Intel, на других архитектурах будут использоваться различные механизмы. Время выполнения повторится.