2012-04-11 11 views
25

Qualcuno può spiegare come funziona atomicModifyIORef? In particolare:Haskell: Come funziona 'atomicModifyIORef'?

(1) Attende un blocco o tenta ottimisticamente di riprovare in caso di contesa (come TVar).
(2) Perché la firma di atomicModifyIORef è diversa dalla firma di modifyIORef? In particolare, qual è questa variabile extra b?

Edit: credo di aver capito la risposta (2), in quanto b è un valore da estrarre (questo può essere vuoto se non necessario). In un programma a thread singolo, conoscendo il valore è banale, ma in un programma multithread, si potrebbe voler sapere qual era il valore precedente al momento dell'applicazione della funzione. Suppongo che questo è il motivo per modifyIORef non ha questo valore di ritorno extra (come tali usi di modifyIORef con questo valore di ritorno probabilmente dovrebbero usare atomicModifyIORef comunque. Sono ancora interessato nella risposta alla (1) però.

risposta

10

atomicModifyIORef prende un r :: IORef a e una funzione f :: a -> (a, b) e fa quanto segue:..

e 'legge il valore di r e si applica f a questo valore, cedendo (a',b) Poi il r viene aggiornato con il nuovo valore a' mentre b è il valore di ritorno questa lettura e l'accesso in scrittura avviene atomicamente

Ovviamente questa atomicità funziona solo se tutti gli accessi a r vengono eseguiti tramite atomicModifyIORef. Si noti che è possibile trovare queste informazioni osservando la fonte [1].

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

+3

Esegue il blocco o è ottimista? La versione GHC sembra solo chiamare una primitiva GHC. – Clinton

+1

Si noti che a causa della pigrizia, 'atomicModifyIORef' deve solo cambiare il valore corrente per puntare a un thunk, con il lavoro effettivo che viene ritardato fino a quando non viene letto in un secondo momento. AFAIK, si compila in qualcosa come un CAS sulla maggior parte delle piattaforme. – hammar

+10

Ottimista, tramite un circuito di scambio (cas) interbloccato. https://github.com/ghc/ghc/blob/45740c29b24ea78b885d3b9f737a8bdc00265f7c/rts/PrimOps.cmm#L364 –

27

lo fa attendere una serratura, o ottimisticamente cercare di riprovare se c'è contesa (come Tvar).

atomicModifyIORef utilizza un'istruzione di blocco sull'architettura hardware sottostante su cui ci si trova, per scambiare il puntatore con un oggetto Haskell assegnato in modo atomico.

Su x86 che utilizza l'intruction cas, esposti come un primitivo alla lingua tramite atomicModifyMutVar#, che viene implementato come un servizio runtime in Cmm come:

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 
... 
} 

Cioè, si cercherà di fare lo swap e riprova altrimenti.

L'attuazione di CAS come mostra primitivi come scendere al metallo:

/* 
* 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; 

Così si può vedere che è in grado di utilizzare un'istruzione atomica in Intel, su altre architetture differenti meccanismi saranno Usato. Il runtime riproverà.