Interlocked.Exchange和Volatile.Write有什么区别?
两种方法都更新某些变量的值.有人可以总结何时使用它们?
http://msdn.microsoft.com/ru-ru/library/bb337971 和http://msdn.microsoft.com/en-us/library/gg712713.aspx
特别是我需要更新我的数组的双项,我希望另一个线程看到最新的值.什么是首选?Interlocked.Exchange(ref arr[3], myValue)或Volatile.Write(ref arr[3], info);在那里arr被声明为double?
================================================== ==========================真实的例子,我声明了这样的双数组:
private double[] _cachedProduct;
Run Code Online (Sandbox Code Playgroud)
在一个线程中,我更新它:
_cachedProduct[instrumentId] = calcValue;
...
are.Set();
Run Code Online (Sandbox Code Playgroud)
在另一个线程中,我像这样读取这个数组:
while(true) {
are.WaitOne();
...
result += _cachedProduct[instrumentId];
...
}
Run Code Online (Sandbox Code Playgroud)
对我来说它只是工作正常.然而,为了确保"它将永远有效",无论它看起来我应该添加Volatile.Write或Interlocked.Exchange.因为双重更新不能保证是原子的http://msdn.microsoft.com/en-us/library/aa691278%28VS.71%29.aspx
在这个问题的答案中,我希望看到Volatile和Interlocked类的详细比较.为什么我们需要2节课?哪一个和何时使用?
我已经看到了堆栈的一些无锁实现...我的问题是关于可见性,而不是原子性.例如,无锁堆栈的元素(不是指针)必须至多为64位?我是这么认为的,因为你无法保证知名度.真实示例:可以安全地插入此结构并从无锁容器中删除
struct person
{
string name;
uint32_t age;
}
Run Code Online (Sandbox Code Playgroud)
编辑:有些人对这个问题感到困惑.为了解释一下:如果作家把人推到堆栈上,读者就会得到它,是否保证读者看到(记忆可见性)正确的人的内容.
在涉及线程时,试图理解.net的内存模型.这个问题是严格的理论问题,我知道它可以通过其他方式解决,例如使用lock或标记_task为volatile.
以下面的代码为例:
class Test
{
Task _task;
int _working = 0;
public void Run()
{
if (Interlocked.CompareExchange(ref _working, 1, 0) == 0)
{
_task = Task.Factory.StartNew(() =>
{
//do some work...
});
_task.ContinueWith(antecendent => Interlocked.Exchange(ref _working, 0));
}
}
public void Dispose()
{
if (Interlocked.CompareExchange(ref _working, _working, 0) == 1)
{
_task.ContinueWith(antecendent => { /*do some other work*/ });
}
}
}
Run Code Online (Sandbox Code Playgroud)
现在做出以下假设:
Run可以多次调用(来自不同的线程),并且在调用之后永远不会Dispose被调用.Dispose 将被称为恰好一次.现在我的问题是,_task(在Dispose方法中)的值是否总是一个"新的"值,这意味着它是从"主存储器"中读取而不是从寄存器中读取的?从我一直在阅读的内容 …
考虑一个原子读-修改-写操作,例如x.exchange(..., std::memory_order_acq_rel)。出于对其他对象的加载和存储进行排序的目的,这是否被视为:
具有获取-释放语义的单个操作?
或者,作为一个获取加载,然后是一个释放存储,附加保证其他加载和存储x将同时观察它们或两者都不观察?
如果它是 #2,那么尽管在加载之前或存储之后不能对同一线程中的其他操作进行重新排序,但仍然存在在两者之间重新排序的可能性。
作为一个具体的例子,考虑:
std::atomic<int> x, y;
void thread_A() {
x.exchange(1, std::memory_order_acq_rel);
y.store(1, std::memory_order_relaxed);
}
void thread_B() {
// These two loads cannot be reordered
int yy = y.load(std::memory_order_acquire);
int xx = x.load(std::memory_order_acquire);
std::cout << xx << ", " << yy << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
可以thread_B输出0, 1吗?
如果x.exchange()换成了x.store(1, std::memory_order_release);那么thread_B肯定能输出0, 1。是否应该exchange()排除额外的隐式负载?
cppreference听起来像 #1 是这种情况并且0, 1被禁止:
具有此内存顺序的读-修改-写操作既是获取操作又是释放操作。当前线程中的任何内存读取或写入都不能在此存储之前或之后重新排序。
但是我在标准中找不到任何明确的内容来支持这一点。实际上,该标准对原子读-修改-写操作几乎没有说明,除了 N4860 …
假设我有一个变量"counter",并且有几个线程通过使用Interlocked访问和设置"counter"的值,即:
int value = Interlocked.Increment(ref counter);
Run Code Online (Sandbox Code Playgroud)
和
int value = Interlocked.Decrement(ref counter);
Run Code Online (Sandbox Code Playgroud)
我可以假设,Interlocked所做的更改将在所有线程中可见吗?
如果没有,我该怎么做才能使所有线程同步变量?
编辑:有人建议我使用volatile.但是当我将"计数器"设置为volatile时,会出现编译器警告"对volatile字段的引用不会被视为volatile".
当我阅读在线帮助时,它说:"通常不应使用ref或out参数传递易失性字段".
根据这个问题的答案,似乎x86上的LOCK CMPXCHG实际上会导致完全屏障.据推测,这也是Unsafe.compareAndSwapInt()引擎盖下产生的.我很难理解为什么会这样:使用MESI协议,在更新缓存行之后,CPU是否只会使其他内核上的缓存行无效,而不是耗尽执行CAS的核心的所有存储/加载缓冲区?似乎对我很浪费......
我正在使用F#中的异步工作流和代理工作很多,而我对事件的深入了解我注意到Event <_>()类型不是线程安全的.在这里,我不是在谈论举办活动的常见问题.我实际上是在谈论订阅和删除/处理事件.出于测试目的,我写了这个简短的程序
let event = Event<int>()
let sub = event.Publish
[<EntryPoint>]
let main argv =
let subscribe sub x = async {
let mutable disposables = []
for i=0 to x do
let dis = Observable.subscribe (fun x -> printf "%d" x) sub
disposables <- dis :: disposables
for dis in disposables do
dis.Dispose()
}
Async.RunSynchronously(async{
let! x = Async.StartChild (subscribe sub 1000)
let! y = Async.StartChild (subscribe sub 1000)
do! x
do! y
event.Trigger 1
do! Async.Sleep 2000
}) …Run Code Online (Sandbox Code Playgroud) 看起来单声道实现MemoryBarrier在ReaderWriterLockSlim方法内没有调用.因此,当我在a中进行任何更改时write lock,我可以在另一个使用a的线程中接收旧的缓存值read lock.
真的有可能吗?我应该MemoryBarrier在代码内部读取和写入锁定之前和之后插入?
我有一个偶尔会出现故障的程序,我想知道这些问题是否与运行在不同内核上的不同线程有关,处理读写的顺序不同(我知道x86内存模型需要不同的内核才能完成预期的工作但是在某些情况下,读取和写入无法在单个CPU上重新排序,但可能在多核系统上重新排序.将处理器亲和性设置为某些特定选择的任意CPU核心似乎不是一个好主意(如果该核心发生繁忙,没有理由所有线程都不能迁移到其他核心,只要有一个完整的缓存先冲洗).有没有办法简单地指示所有线程必须在同一个核心上运行,但我不关心它是哪一个?
PS -我的理解是,如果一个线程将一些数据写入类实例,然后对类引用执行CompareExchange(因此引用将指向新修改的实例),这意味着对实例的所有更改都将是在课程参考之前写到内存; 在使用该类引用的同一CPU上的另一个线程上运行的代码将使用类引用的旧值或将看到对该实例所做的更改; 但是,在其他CPU上运行的代码可能会在某些棘手的情况下看到类引用的新值,但看不到写入实例的新数据.我的理解是错误的吗?
c# ×4
asynchronous ×2
atomic ×2
visibility ×2
.net ×1
.net-4.5 ×1
c++ ×1
events ×1
f# ×1
interlocked ×1
java ×1
lock-free ×1
observable ×1
stdatomic ×1