如何实现" i ++和i> = max? 0:我"只在Go中使用原子

时间:2018-04-03 12:51:23

标签: go locking atomic cas

仅使用原子实现以下代码:

localhost:8080
如下:

const Max = 8
var index int
func add() int {
    index++
    if index >= Max {
        index = 0
    }
    return index
}

但这是错误的。有竞争条件。 可以实现不使用锁?

2 个答案:

答案 0 :(得分:3)

解决它没有循环和锁

一个简单的实现可能如下所示:

const Max = 8

var index int64

func Inc() int64 {
    value := atomic.AddInt64(&index, 1)
    if value < Max {
        return value // We're done
    }

    // Must normalize, optionally reset:
    value %= Max
    if value == 0 {
        atomic.AddInt64(&index, -Max) // Reset
    }
    return value
}

它是如何运作的?

它只是在柜台上加1; atomic.AddInt64()返回新值。如果它低于Max,&#34;我们已经完成&#34;,我们可以退货。

如果它大于或等于Max,那么我们必须对值进行标准化(确保它在[0..Max)范围内)并重置计数器。< / p>

重置可能只能由一个调用者(一个goroutine)完成,该调用者将由计数器的值选择。获胜者将是导致该计数器达到Max的人。

避免需要锁定的技巧是通过添加-Max来重置它,而不是通过将其设置为0来重置它。由于计数器的值已经标准化,如果其他goroutine正在调用它并同时递增它,它将不会导致任何问题。

当然,由于许多goroutine同时调用此Inc(),因此在应该重置它的goroutine之前,计数器可能会增加Max次,实际上可以执行重置。导致计数器达到或超过2 * Max甚至3 * Max(通常为n * Max)。因此,我们通过使用value % Max == 0条件来决定是否应该重置,这也只会在n的每个可能值的单个goroutine上发生。

简化

请注意,规范化不会更改范围[0..Max)中已有的值,因此您可以选择始终执行规范化。如果您愿意,可以将其简化为:

func Inc() int64 {
    value := atomic.AddInt64(&index, 1) % Max
    if value == 0 {
        atomic.AddInt64(&index, -Max) // Reset
    }
    return value
}

在不增加计数器的情况下读取计数器

不应直接访问index变量。如果需要在不增加计数器的情况下读取计数器的当前值,可以使用以下功能:

func Get() int64 {
    return atomic.LoadInt64(&index) % Max
}

极端情况

让我们来分析一个极端的&#34;场景。在此,Inc()被调用7次,返回数字1..7。现在,在递增后下一次调用Inc()将会看到计数器位于8 = Max。然后,它会将值标准化为0,并希望重置计数器。现在让我们说在重置之前(即添加-8)实际执行,其他8个调用发生。他们将计数器递增8次,最后一次将再次看到计数器的值为16 = 2 * Max。所有调用都会将值标准化为范围0..7,最后一次调用将再次执行重置。让我们说这个重置再次被延迟(例如出于调度原因),还有另外8个呼叫进来。最后,计数器的值将是24 = 3 * Max,最后一次呼叫将再次出现继续执行重置。

请注意,所有调用只会返回[0..Max)范围内的值。执行所有重置操作后,计数器的值将正确0,因为它的值为24且有3&#34;等待&#34;重置操作。在实践中,只有很小的机会发生这种情况,但是这个解决方案可以很好地有效地处理它。

答案 1 :(得分:1)

我认为您的目标是永远不要让index的值等于或大于Max。这可以使用CAS(比较和交换)循环来解决:

const Max = 8
var index int32

func add() int32 {
    var next int32;

    for {
        prev := atomic.LoadInt32(&index)
        next = prev + 1;

        if next >= Max {
            next = 0
        }

        if (atomic.CompareAndSwapInt32(&index, prev, next)) {
            break;
        }
    }

    return next
}

CAS可用于像这样原子地实现几乎任何操作。算法是:

  1. 加载值
  2. 执行所需的操作
  3. 使用CAS,在失败时转到1。