可以Interlocked类安全地与锁混合()? [英] Can Interlocked class safely be mixed with lock()?

查看:263
本文介绍了可以Interlocked类安全地与锁混合()?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

是当你用锁()(和其他更高级别的锁)混合互锁操作保证一个原子读?



我感兴趣的一般行为混合时锁定。机制就是这样,和的Int32和Int64的之间的差异

 私人的Int64计数; 
私有对象_myLock;

公众的Int64计数
{
得到
{
锁(_myLock)
{
返回计数;
}
}
}

公共无效的增量
{
Interlocked.Increment(引用计数);
}


解决方案

注意:此给出的答案(其中包括已经在两个编辑)的问题改为有很长的计数之前。对于当前的问题,而不是 Thread.VolatileRead 我会使用 Interlocked.Read ,其中也有挥发性的语义,也将对付这里讨论并引入问题的64位读问题



原子的读是无锁保证的,因为正确对齐值读取32位以内,你的计数的,保证是原子的。



这不同于64比特价值的地方,如果它开始在-1,而另一个线程递增被读取,可能会导致读取值-1是(增加发生过),0(增量发生后)或任4294967295或-4294967296(写0 32位,32位等待写)。



Interlocked.Increment 的原子增量意味着整个递增操作是原子。考虑到增量概念:




  1. 读取值

  2. 添加一个值

  3. 写的值。



那么,如果 X 54,一个线程试图增加它,而另一个试图将其设置为67,两个正确的可能值为67(增量首先发生,然后写了)或68(分配情况,然后再递增),但非原子增量可导致55(增加读取,67分配情况,增量写入)。



一个比较常见的真实案例为 X 54,一个线程递增和递减另一个。在这里,唯一有效的结果是54(一涨,再一跌,反之亦然),但如果没有原子那么可能的结果是53,54和55。



如果你只是想是原子递增计数,正确的代码是:

 私人诠释计数; 

公众诠释伯爵
{
得到
{
返回Thread.VolatileRead(按地址数);
}
}

公共无效的增量
{
Interlocked.Increment(计数);
}



然而,如果你想在那算不算采取行动,那么它将需要更强的锁定。这是因为使用的计线程可以成为过时结束其操作之前。在这种情况下,你需要锁定在乎,改变它的计数和一切的一切。究竟这需要做(不论它甚至重要做的话)取决于你的使用情况比可以从你的问题可以推断的更多的问题。



编辑:哦,你可能想简单地锁定强制内存屏障。你也可能需要更改计数的实施返回Thread.VolatileRead(引用计数); ,以确保CPU如果你要解除锁定缓存刷新。这取决于重要缓存过时是如何在这种情况下。 (另一种方法是让计数不稳定,因为那么所有的读取和写入会挥发。请注意,这是没有必要的互锁,因为他们总是易失的操作)



编辑2:其实,你这么容易想这个动荡的阅读,我改变了回答以上。它的你可能不会在意它所提供的,但不太可能。


Is an atomic read guaranteed when you mix Interlocked operations with lock() (and other higher-level locks)?

I am interested in general behaviour when mixing locking mechanisms like this, and any differences between Int32 and Int64.

private Int64 count;
private object _myLock;

public Int64 Count 
{
  get 
  {
    lock(_myLock)
    {
      return count;
    }
  }
}

public void Increment
{
  Interlocked.Increment(ref count);
}

解决方案

Note: This answer (including the two edits already) was given before the question was changed to have a long count. For the current question instead of Thread.VolatileRead I would use Interlocked.Read, which also has volatile semantics and would also deal with the 64-bit read issue discussed here and introduced into the question

An atomic read is guaranteed without locking, because reads of properly-aligned values of 32-bit or less, which your count is, are guaranteed to be atomic.

This differs from a 64-bit value where if it started at -1, and was read while another thread was incrementing it, could result in the value read being -1 (happened before increment), 0 (happened after increment) or either of 4294967295 or -4294967296 (32 bits written to 0, other 32bits awaiting write).

The atomic increment of Interlocked.Increment means that the whole increment operation is atomic. Consider that increment is conceptually:

  1. Read the value.
  2. Add one to the value.
  3. Write the value.

Then if x is 54 and one thread tries to increment it while another tries to set it to 67, the two correct possible values are 67 (increment happens first, then is written over) or 68 (assignment happens first, then is incremented) but non-atomic increment can result in 55 (increment reads, assignment of 67 happens, increment writes).

A more common real case is x is 54 and one thread increments and another decrements. Here the only valid result is 54 (one up, then one down, or vice-versa), but if not atomic then possible results are 53, 54 and 55.

If you just want a count that is incremented atomically, the correct code is:

private int count;

public int Count 
{
  get 
  {
    return Thread.VolatileRead(byref count);
  }
}

public void Increment
{
  Interlocked.Increment(count);
}

If however you want to act upon that count, then it will need stronger locking. This is because the thread using the count can become out of date before its operation is finished. In this case you need to lock on everything that cares about the count and everything that changes it. Just how this need be done (and whether its even important to do at all) depends on more matters of your use-case than can be inferred from your question.

Edit: Oh, you may want to lock simply to force a memory barrier. You may also want to change Count's implementation to return Thread.VolatileRead(ref count); to make sure CPU caches are flushed if you are going to remove the lock. It depends on how important cache staleness is in this case. (Another alternative is to make count volatile, as then all reads and writes will be volatile. Note that this isn't needed for the Interlocked operations as they are always volatile.)

Edit 2: Indeed, you're so likely to want this volatile read, that I'm changing the answer above. It is possible you won't care about what it offers, but much less likely.

这篇关于可以Interlocked类安全地与锁混合()?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆