【问题标题】:Haskell: How does 'atomicModifyIORef' work?Haskell:“atomicModifyIORef”是如何工作的?
【发布时间】:2012-04-23 13:51:41
【问题描述】:

有人能解释一下atomicModifyIORef 的工作原理吗?特别是:

(1) 是等待锁定,还是乐观地尝试并在存在争用时重试(如TVar)。
(2) 为什么atomicModifyIORef的签名和modifyIORef的签名不一样?特别是b这个额外的变量是什么?

编辑: 我想我已经找到了 (2) 的答案,因为 b 是要提取的值(如果不需要,可以为空)。在单线程程序中,知道该值是微不足道的,但在多线程程序中,人们可能想知道在应用函数时之前的值是什么。我认为这就是为什么modifyIORef 没有这个额外的返回值(因为modifyIORef 与这个返回值的这种用法可能应该使用atomicModifyIORef。不过,我仍然对(1)的答案感兴趣。

【问题讨论】:

    标签: haskell concurrency locking atomic compare-and-swap


    【解决方案1】:

    atomicModifyIORef 接受 r :: IORef a 和函数 f :: a -> (a, b) 并执行以下操作:

    它读取r 的值并将f 应用于该值,产生(a',b)。然后r 用新值a' 更新,而b 是返回值。这种读写访问是原子完成的。

    当然,这种原子性只有在所有r的访问都是通过atomicModifyIORef完成时才有效。 请注意,您可以通过查看来源 [1] 找到此信息。

    [1]https://hackage.haskell.org/package/base-4.12.0.0/docs/Data-IORef.html#v:atomicModifyIORef

    【讨论】:

    • 它执行锁定还是乐观? GHC 版本似乎只是调用 GHC 原语。
    • 请注意,由于懒惰,atomicModifyIORef 只需将当前值更改为指向 thunk,实际工作会延迟到稍后读取。 AFAIK,它在大多数平台上都可以编译成 CAS 之类的东西。
    • 乐观,通过互锁交换 (cas) 循环。 github.com/ghc/ghc/blob/…
    • @Clinton:线程将按某种顺序创建代表更新的 thunk。接下来会发生什么取决于这些 thunk 的样子,但如果新值取决于旧值,则无论哪个线程先出现都会评估它并用结果覆盖 thunk。如果两个线程同时尝试评估它,则结果取决于称为“黑洞”的东西,其中正在评估的 thunk 可能被标记为“黑洞”。如果另一个线程试图评估黑洞,它将阻塞,直到第一个线程完成评估。
    • [cont'd] 两个线程也有可能同时评估一个 thunk。因为这是纯代码,所以这是安全的,而且通常不是什么大问题,因为 thunk 往往很小,但仍然可以使用编译器标志来调整它以启用“eager blackholing”,这意味着所有正在评估的 thunk标记为黑洞,而通常只有在原始线程由于某种原因阻塞或进行垃圾收集时才会发生这种情况。
    【解决方案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 中使用原子指令,在其他架构上将使用不同的机制。运行时将重试。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-04-02
      • 2011-05-22
      • 2016-12-06
      • 2012-09-08
      • 2011-04-21
      • 2011-04-08
      相关资源
      最近更新 更多