New to this website, so let me know if I'm not posting in an accepted manner.
这个网站的新用户,如果我没有以可接受的方式发帖,请告诉我。
I've frequently coded something along the lines of the sample below(with stuff like Dispose ommited for clarity. ). My question is, are the volatiles needed as shown? Or does the ManualResetEvent.Set have an implicit memory barrier as I've read Thread.Start does? Or would an explicit MemoryBarrier call be better than the volatiles? Or is it completely wrong? Also, the fact that the "implicit memory barrier behavior" in some operations is not documented as far as I've seen is quite frutrating, is there a list of these operations somewhere?
我经常在下面的示例中编写一些代码(为了清楚起见,使用Dispose ommtited等内容)。我的问题是,如图所示需要挥发物吗?或者,当我读过Thread.Start时,ManualResetEvent.Set是否有隐式内存屏障?或者显式的MemoryBarrier调用是否比volatile更好?还是完全错了?此外,据我所见,某些操作中的“隐式记忆障碍行为”没有记录,这是非常恐怖的,这些操作的列表是否存在?
Thanks, Tom
谢谢,汤姆
:
:
class OneUseBackgroundOp
{
// background args
private string _x;
private object _y;
private long _z;
// background results
private volatile DateTime _a
private volatile double _b;
private volatile object _c;
// thread control
private Thread _task;
private ManualResetEvent _completedSignal;
private volatile bool _completed;
public bool DoSomething(string x, object y, long z, int initialWaitMs)
{
bool doneWithinWait;
_x = x;
_y = y;
_z = z;
_completedSignal = new ManualResetEvent(false);
_task = new Thread(new ThreadStart(Task));
_task.IsBackground = true;
_task.Start()
doneWithinWait = _completedSignal.WaitOne(initialWaitMs);
return doneWithinWait;
}
public bool Completed
{
get
{
return _completed;
}
}
/* public getters for the result fields go here, with an exception
thrown if _completed is not true; */
private void Task()
{
// args x, y, and z are written once, before the Thread.Start
// implicit memory barrier so they may be accessed freely.
// possibly long-running work goes here
// with the work completed, assign the result fields _a, _b, _c here
_completed = true;
_completedSignal.Set();
}
}
5 个解决方案
#1
3
Note that this is off the cuff, without studying your code closely. I don't think Set performs a memory barrier, but I don't see how that's relevant in your code? Seems like more important would be if Wait performs one, which it does. So unless I missed something in the 10 seconds I devoted to looking at your code, I don't believe you need the volatiles.
请注意,这不是袖口,没有仔细研究您的代码。我不认为Set会执行内存屏障,但我不知道你的代码中的相关性如何?看起来更重要的是,如果Wait执行一个,它会这样做。因此,除非我在10秒内错过了一些我专注于查看代码的内容,否则我不相信你需要挥发性物质。
Edit: Comments are too restrictive. I'm now referring to Matt's edit.
编辑:评论过于严格。我现在指的是马特的编辑。
Matt did a good job with his evaluation, but he's missing a detail. First, let's provide some definitions of things thrown around, but not clarified here.
马特在评估方面做得很好,但他错过了一个细节。首先,让我们提供一些抛出的东西的定义,但这里没有说明。
A volatile read reads a value and then invalidates the CPU cache. A volatile write flushes the cache, and then writes the value. A memory barrier flushes the cache and then invalidates it.
易失性读取读取值,然后使CPU高速缓存无效。易失性写入会刷新缓存,然后写入值。内存屏障刷新缓存然后使其无效。
The .NET memory model ensures that all writes are volatile. Reads, by default, are not, unless an explicit VolatileRead is made, or the volatile keyword is specified on the field. Further, interlocked methods force cache coherency, and all of the synchronization concepts (Monitor, ReaderWriterLock, Mutex, Semaphore, AutoResetEvent, ManualResetEvent, etc.) call interlocked methods internally, and thus ensure cache coherency.
.NET内存模型确保所有写入都是易失性的。默认情况下,读取不是,除非创建了显式的VolatileRead,或者在字段上指定了volatile关键字。此外,互锁方法强制缓存一致性,并且所有同步概念(Monitor,ReaderWriterLock,Mutex,Semaphore,AutoResetEvent,ManualResetEvent等)在内部调用互锁方法,从而确保缓存一致性。
Again, all of this is from Jeffrey Richter's book, "CLR via C#".
同样,所有这些都来自Jeffrey Richter的书“CLR via C#”。
I said, initially, that I didn't think Set performed a memory barrier. However, upon further reflection about what Mr. Richter said, Set would be performing an interlocked operation, and would thus also ensure cache coherency.
我说,最初,我不认为Set执行了内存屏障。然而,在进一步思考里希特先生所说的内容之后,Set将执行互锁操作,因此也将确保缓存一致性。
I stand by my original assertion that volatile is not needed here.
我坚持原来的断言,这里不需要挥发性。
Edit 2: It looks as if you're building a "future". I'd suggest you look into PFX, rather than rolling your own.
编辑2:看起来你正在构建一个“未来”。我建议你研究PFX,而不是自己动手。
#2
3
The volatile keyword should not confused as to making _a, _b, and _c thread-safe. See here for a better explanation. Further, the ManualResetEvent does not have any bearing on the thread-safety of _a, _b, and _c. You have to manage that separately.
volatile关键字不应该与使_a,_b和_c线程安全相混淆。请参阅此处以获得更好的解释。此外,ManualResetEvent对_a,_b和_c的线程安全性没有任何影响。你必须单独管理它。
EDIT: With this edit, I am attempting to distill all of the information that has been put in various answers and comments regarding this question.
编辑:通过此编辑,我试图提取有关此问题的各种答案和评论中提供的所有信息。
The basic question is whether or not the result variables (_a, _b, and _c) will be 'visible' at the time the flag variable (_completed) returns true.
基本问题是在变量(_completed)返回true时,结果变量(_a,_b和_c)是否为“可见”。
For a moment, let's assume that none of the variables are marked volatile. In this case, it would be possible for the result variables to be set after the flag variable is set in Task(), like this:
暂时,让我们假设没有变量标记为volatile。在这种情况下,可以在Task()中设置标志变量之后设置结果变量,如下所示:
private void Task()
{
// possibly long-running work goes here
_completed = true;
_a = result1;
_b = result2;
_c = result3;
_completedSignal.Set();
}
This is clearly not what we want, so how do we deal with this?
这显然不是我们想要的,所以我们如何处理这个?
If these variables are marked volatile, then this reordering will be prevented. But that is what prompted the original question - are the volatiles required or does the ManualResetEvent provide an implicit memory barrier such that reordering does not occur, in which case the volatile keyword is not really necessary?
如果这些变量标记为volatile,则将阻止此重新排序。但这就是提出原始问题的原因 - 是需要挥发性还是ManualResetEvent提供了一个隐式内存屏障,这样就不会发生重新排序,在这种情况下,volatile关键字不是真的必要吗?
If I understand correctly, wekempf's position is that the WaitOne() function provides an implicit memory barrier which fixes the problem. BUT that doesn't seem sufficient to me. The main and background threads could be executing on two separate processors. So, if Set() does not also provide an implicit memory barrier, then the Task() function could end up being executed like this on one of the processors (even with the volatile variables):
如果我理解正确,wekempf的立场是WaitOne()函数提供了一个隐式内存屏障来解决问题。但这对我来说似乎不够。主线程和后台线程可以在两个单独的处理器上执行。因此,如果Set()也没有提供隐式内存屏障,那么Task()函数最终可能会在其中一个处理器上执行(即使使用volatile变量):
private void Task()
{
// possibly long-running work goes here
_completedSignal.Set();
_a = result1;
_b = result2;
_c = result3;
_completed = true;
}
I have searched high and low for information regarding memory barriers and the EventWaitHandles, and I have come up with nothing. The only reference I have seen is the one wekempf has made to Jeffrey Richter's book. The problem I have with this is that the EventWaitHandle is meant to synchronize threads, not access to data. I have never seen any example where EventWaitHandle (e.g., ManualResetEvent) is used to synchronize access to data. As such, I'm hard-pressed to believe that EventWaitHandle does anything with regard to memory barriers. Otherwise, I would expect to find some reference to this on the internet.
我已经搜索了有关内存障碍和EventWaitHandles的信息,我已经找不到了。我见过的唯一一个参考是wekempf对杰弗里里希特的书所做的。我遇到的问题是EventWaitHandle用于同步线程,而不是访问数据。我从未见过使用EventWaitHandle(例如,ManualResetEvent)来同步数据访问的任何示例。因此,我很难相信EventWaitHandle会对内存障碍做任何事情。否则,我希望在互联网上找到一些参考。
EDIT #2: This is a response to wekempf's response to my response... ;)
编辑#2:这是对wekempf对我的回复的回应的回应......;)
I managed to read the section from Jeffrey Richter's book at amazon.com. From page 628 (wekempf quotes this too):
我设法阅读了Jeffrey Richter在amazon.com上的书中的部分。从第628页开始(wekempf引用了这个):
Finally, i should point out that whenever a thread calls an interlocked method, the CPU forces cache coherency. So if you are manipulating variables via interlocked methods, you do not have to worry about all of this memory model stuff. Furthermore, all thread synchronization locks (Monitor, ReaderWriterLock, Mutex, Semaphone, AutoResetEvent, ManualResetEvent, etc.) call interlocked methods internally.
最后,我应该指出,每当一个线程调用一个互锁方法时,CPU就会强制缓存一致性。因此,如果您通过互锁方法操作变量,则不必担心所有这些内存模型的内容。此外,所有线程同步锁(Monitor,ReaderWriterLock,Mutex,Semaphone,AutoResetEvent,ManualResetEvent等)都在内部调用互锁方法。
So it would seem that, as wekempf pointed out, that the result variables do not require the volatile keyword in the example as shown since the ManualResetEvent ensures cache coherency.
因此,正如wekempf指出的那样,似乎结果变量不需要示例中的volatile关键字,因为ManualResetEvent确保了缓存一致性。
Before closing this edit, there are two additional points I'd like to make.
在关闭此编辑之前,还有两点我想做。
First, my initial assumption was that the background thread would potentially run multiple times. I obviously overlooked the name of the class (OneUseBackgroundOp)! Given that it is only run once, it is not clear to me why the DoSomething() function calls WaitOne() in the manner that it does. What is the point of waiting initialWaitMs milliseconds if the background thread may or may not be done at the time DoSomething() returns? Why not just kickoff the background thread and use a lock to synchronize access to the results variables OR simply execute the contents of the Task() function as part of the thread that calls DoSomething()? Is there a reason not to do this?
首先,我最初的假设是后台线程可能会多次运行。我显然忽略了班级的名字(OneUseBackgroundOp)!鉴于它只运行一次,我不清楚为什么DoSomething()函数以它的方式调用WaitOne()。如果在DoSomething()返回时可能会或可能不会执行后台线程,那么等待initialWaitMs毫秒的重点是什么?为什么不启动后台线程并使用锁来同步对结果变量的访问,或者只是执行Task()函数的内容作为调用DoSomething()的线程的一部分?有没有理由不这样做?
Second, it seems to me that not using some kind of locking mechanism on the results variables is still a bad approach. True, it is not needed in the code as shown. But at some point down the road, another thread may come along and try to access the data. It would be better in my mind to prepare for this possibility now rather than try to track down mysterious behavior anomalies later.
其次,在我看来,在结果变量上不使用某种锁定机制仍然是一种不好的方法。没错,代码中不需要它,如图所示。但在某些时候,另一个线程可能会出现并尝试访问数据。在我看来,现在更好地为这种可能性做准备,而不是试图追踪以后的神秘行为异常。
Thanks to everyone for bearing with me on this. I've certainly learned a lot by participating in this discussion.
感谢大家对我的关注。通过参与这次讨论,我确实学到了很多东西。
#3
3
Wait functions have an implicit memory barrier. See http://msdn.microsoft.com/en-us/library/ms686355(v=vs.85).aspx
等待函数具有隐式内存屏障。请参阅http://msdn.microsoft.com/en-us/library/ms686355(v=vs.85).aspx
#4
1
First, I'm not sure if I should "Answer my own question" or use a comment for this, but here goes:
首先,我不确定我是否应该“回答我自己的问题”或对此进行评论,但这里是:
My understanding is that volatile prevents code/memory optimizations from moving the accesses to my result variables (and the completed boolean) such that the the thread that reads the result will see upt-to-date data.
我的理解是volatile会阻止代码/内存优化将访问移动到我的结果变量(和完成的布尔值),以便读取结果的线程将看到最新的数据。
You wouldn't want the _completed boolean made visible to all threads after the Set() due to compiler or emmpry optimaztions/reordering. Likewise, you wouldn't want the writes to the results _a, _b, _c being seen after the Set().
由于编译器或emmpry optimaztions / reordering,你不希望在Set()之后使所有线程看到_completed布尔值。同样,您不希望在Set()之后看到对结果_a,_b,_c的写入。
EDIT: Further explaination/clarification on the question, in regards to items mentioned by Matt Davis:
编辑:关于问题的进一步解释/澄清,关于马特戴维斯提到的项目:
Finally, i should point out that whenever a thread calls an interlocked method, the CPU forces cache coherency. So if you are manipulating variables via interlocked methods, you do not have to worry about all of this memory model stuff. Furthermore, all thread synchronization locks (Monitor, ReaderWriterLock, Mutex, Semaphone, AutoResetEvent, ManualResetEvent, etc.) call interlocked methods internally.
最后,我应该指出,每当一个线程调用一个互锁方法时,CPU就会强制缓存一致性。因此,如果您通过互锁方法操作变量,则不必担心所有这些内存模型的内容。此外,所有线程同步锁(Monitor,ReaderWriterLock,Mutex,Semaphone,AutoResetEvent,ManualResetEvent等)都在内部调用互锁方法。
So it would seem that, as wekempf pointed out, that the result variables do not require the volatile keyword in the example as shown since the ManualResetEvent ensures cache coherency.
因此,正如wekempf指出的那样,似乎结果变量不需要示例中的volatile关键字,因为ManualResetEvent确保了缓存一致性。
So you are both in agreement that such an operation takes care of caching between processors or in registers etc.
所以你们都同意这样的操作会处理处理器之间或寄存器等的缓存。
But does it prevent reording to guarantee such that BOTH the results are assigned before the completed flag, and that the completed flag is assigned true before the ManualResetEvent is Set?
但它是否会阻止重新保证,以便在完成标志之前分配结果,并且在设置ManualResetEvent之前将完成标志分配为true?
First, my initial assumption was that the background thread would potentially run multiple times. I obviously overlooked the name of the class (OneUseBackgroundOp)! Given that it is only run once, it is not clear to me why the DoSomething() function calls WaitOne() in the manner that it does. What is the point of waiting initialWaitMs milliseconds if the background thread may or may not be done at the time DoSomething() returns? Why not just kickoff the background thread and use a lock to synchronize access to the results variables OR simply execute the contents of the Task() function as part of the thread that calls DoSomething()? Is there a reason not to do this?
首先,我最初的假设是后台线程可能会多次运行。我显然忽略了班级的名字(OneUseBackgroundOp)!鉴于它只运行一次,我不清楚为什么DoSomething()函数以它的方式调用WaitOne()。如果在DoSomething()返回时可能会或可能不会执行后台线程,那么等待initialWaitMs毫秒的重点是什么?为什么不启动后台线程并使用锁来同步对结果变量的访问,或者只是执行Task()函数的内容作为调用DoSomething()的线程的一部分?有没有理由不这样做?
The concept of the sample is to execute a possibly long-runnig task. If the task can be completed within an exceptable amount of time, then the calling thread will get access to the result and continue with normal processing. But sometime a task can take quite a long time and the claiing thread cannot be blocked for that period and can take reasonable steps to deal with that. That can include checking back later on the operation using the Completed property.
该示例的概念是执行可能长期运行的任务。如果任务可以在一段不可及的时间内完成,那么调用线程将获得对结果的访问权并继续正常处理。但是有时某项任务可能需要很长时间才能完成,并且在此期间无法阻止攻击线程,并且可以采取合理的步骤来解决这个问题。这可以包括稍后使用Completed属性检查操作。
A concrete example: A DNS resolve is often very quick (subsecond) and worth waiting for even from a GUI, but sometimes it can take many many seconds. So by using a utility class like the sample, one could gets a result easily from the point-of-view of the caller 95% of the time and not lock up the GUI the other 5%. One could use a Background worker, but that can be overkill for an operation that the vast majority of the time doesn't need all that plumbing.
一个具体的例子:DNS解析通常非常快(亚秒)并且值得等待甚至从GUI,但有时它可能需要很多秒。因此,通过使用类似于示例的实用程序类,可以在95%的时间内从调用者的角度轻松获得结果,而不是将GUI锁定为另外5%。可以使用背景工作者,但对于绝大多数时间不需要所有管道的操作来说,这可能是过度的。
Second, it seems to me that not using some kind of locking mechanism on the results variables is still a bad approach. True, it is not needed in the code as shown.
其次,在我看来,在结果变量上不使用某种锁定机制仍然是一种不好的方法。没错,代码中不需要它,如图所示。
The result (and completed flag) data is meant to be write-once, read-many. If I added a lock to assign the results and flag, I'd also have to lock in my result getters, and I never liked seeing getters lock just to return a data point. From my reading, such fine-grained locking is not appropriate. If an operation has 5 or 6 results, the caller has to take and release the lock 5 or 6 times needlessly.
结果(和完成标志)数据意味着一次写入,多次读取。如果我添加了一个锁以分配结果和标志,我还必须锁定我的结果getter,而且我从不喜欢看到getter锁只是为了返回一个数据点。从我的阅读来看,这种细粒度的锁定是不合适的。如果操作有5或6个结果,则调用者必须不必要地取出和释放锁5或6次。
But at some point down the road, another thread may come along and try to access the data. It would be better in my mind to prepare for this possibility now rather than try to track down mysterious behavior anomalies later.
但在某些时候,另一个线程可能会出现并尝试访问数据。在我看来,现在更好地为这种可能性做准备,而不是试图追踪以后的神秘行为异常。
Because I have a volatile completed flag that is guarenteed to be set before the volatile results are, and the only access to the results is through the getters, and as mentioned in the smaple, an exception is thrown if the getter is called and the operation is not yet complete, I'd expect that the Completed and result getters CAN be invoked by a thread other than the one that called DoSomething(). That's my hope anyway. I believe this to be true with the volatiles anyway.
因为我有一个易失性的完成标志,在挥发性结果之前被设置为保证,并且对结果的唯一访问是通过getter,并且如在smaple中所提到的,如果调用getter并且操作,则抛出异常尚未完成,我希望可以通过调用DoSomething()之外的线程调用Completed和result getter。无论如何,这是我的希望。无论如何,我相信这对挥发物来说也是如此。
#5
0
Based on what you've shown, I would say that, no, the volatiles
are not required in that code.
根据你所展示的内容,我会说,不,该代码中不需要挥发物。
The ManualResetEvent
itself doesn't have an implicit memory barrier. However, the fact that the main thread is waiting for the signal means that it can't modify any variables. At least, it can't modify any variables while it's waiting. So I guess you could say that waiting on a synchronization object is an implicit memory barrier.
ManualResetEvent本身没有隐式内存屏障。但是,主线程正在等待信号这一事实意味着它无法修改任何变量。至少,它在等待时无法修改任何变量。所以我想你可以说等待同步对象是一个隐含的内存障碍。
Note, however, that other threads, if they exist and have access to those variables, could modify them.
但请注意,其他线程(如果存在并且可以访问这些变量)可以修改它们。
From your question, it seems that you're missing the point of what volatile
does. All volatile
does is tell the compiler that the variable might be modified by other threads asynchronously, so it shouldn't optimize code that accesses the variable. volatile
does not in any way synchronize access to the variable.
从你的问题来看,似乎你忽略了挥发性的作用。所有volatile都告诉编译器该变量可能被其他线程异步修改,因此它不应该优化访问该变量的代码。 volatile不会以任何方式同步对变量的访问。
#1
3
Note that this is off the cuff, without studying your code closely. I don't think Set performs a memory barrier, but I don't see how that's relevant in your code? Seems like more important would be if Wait performs one, which it does. So unless I missed something in the 10 seconds I devoted to looking at your code, I don't believe you need the volatiles.
请注意,这不是袖口,没有仔细研究您的代码。我不认为Set会执行内存屏障,但我不知道你的代码中的相关性如何?看起来更重要的是,如果Wait执行一个,它会这样做。因此,除非我在10秒内错过了一些我专注于查看代码的内容,否则我不相信你需要挥发性物质。
Edit: Comments are too restrictive. I'm now referring to Matt's edit.
编辑:评论过于严格。我现在指的是马特的编辑。
Matt did a good job with his evaluation, but he's missing a detail. First, let's provide some definitions of things thrown around, but not clarified here.
马特在评估方面做得很好,但他错过了一个细节。首先,让我们提供一些抛出的东西的定义,但这里没有说明。
A volatile read reads a value and then invalidates the CPU cache. A volatile write flushes the cache, and then writes the value. A memory barrier flushes the cache and then invalidates it.
易失性读取读取值,然后使CPU高速缓存无效。易失性写入会刷新缓存,然后写入值。内存屏障刷新缓存然后使其无效。
The .NET memory model ensures that all writes are volatile. Reads, by default, are not, unless an explicit VolatileRead is made, or the volatile keyword is specified on the field. Further, interlocked methods force cache coherency, and all of the synchronization concepts (Monitor, ReaderWriterLock, Mutex, Semaphore, AutoResetEvent, ManualResetEvent, etc.) call interlocked methods internally, and thus ensure cache coherency.
.NET内存模型确保所有写入都是易失性的。默认情况下,读取不是,除非创建了显式的VolatileRead,或者在字段上指定了volatile关键字。此外,互锁方法强制缓存一致性,并且所有同步概念(Monitor,ReaderWriterLock,Mutex,Semaphore,AutoResetEvent,ManualResetEvent等)在内部调用互锁方法,从而确保缓存一致性。
Again, all of this is from Jeffrey Richter's book, "CLR via C#".
同样,所有这些都来自Jeffrey Richter的书“CLR via C#”。
I said, initially, that I didn't think Set performed a memory barrier. However, upon further reflection about what Mr. Richter said, Set would be performing an interlocked operation, and would thus also ensure cache coherency.
我说,最初,我不认为Set执行了内存屏障。然而,在进一步思考里希特先生所说的内容之后,Set将执行互锁操作,因此也将确保缓存一致性。
I stand by my original assertion that volatile is not needed here.
我坚持原来的断言,这里不需要挥发性。
Edit 2: It looks as if you're building a "future". I'd suggest you look into PFX, rather than rolling your own.
编辑2:看起来你正在构建一个“未来”。我建议你研究PFX,而不是自己动手。
#2
3
The volatile keyword should not confused as to making _a, _b, and _c thread-safe. See here for a better explanation. Further, the ManualResetEvent does not have any bearing on the thread-safety of _a, _b, and _c. You have to manage that separately.
volatile关键字不应该与使_a,_b和_c线程安全相混淆。请参阅此处以获得更好的解释。此外,ManualResetEvent对_a,_b和_c的线程安全性没有任何影响。你必须单独管理它。
EDIT: With this edit, I am attempting to distill all of the information that has been put in various answers and comments regarding this question.
编辑:通过此编辑,我试图提取有关此问题的各种答案和评论中提供的所有信息。
The basic question is whether or not the result variables (_a, _b, and _c) will be 'visible' at the time the flag variable (_completed) returns true.
基本问题是在变量(_completed)返回true时,结果变量(_a,_b和_c)是否为“可见”。
For a moment, let's assume that none of the variables are marked volatile. In this case, it would be possible for the result variables to be set after the flag variable is set in Task(), like this:
暂时,让我们假设没有变量标记为volatile。在这种情况下,可以在Task()中设置标志变量之后设置结果变量,如下所示:
private void Task()
{
// possibly long-running work goes here
_completed = true;
_a = result1;
_b = result2;
_c = result3;
_completedSignal.Set();
}
This is clearly not what we want, so how do we deal with this?
这显然不是我们想要的,所以我们如何处理这个?
If these variables are marked volatile, then this reordering will be prevented. But that is what prompted the original question - are the volatiles required or does the ManualResetEvent provide an implicit memory barrier such that reordering does not occur, in which case the volatile keyword is not really necessary?
如果这些变量标记为volatile,则将阻止此重新排序。但这就是提出原始问题的原因 - 是需要挥发性还是ManualResetEvent提供了一个隐式内存屏障,这样就不会发生重新排序,在这种情况下,volatile关键字不是真的必要吗?
If I understand correctly, wekempf's position is that the WaitOne() function provides an implicit memory barrier which fixes the problem. BUT that doesn't seem sufficient to me. The main and background threads could be executing on two separate processors. So, if Set() does not also provide an implicit memory barrier, then the Task() function could end up being executed like this on one of the processors (even with the volatile variables):
如果我理解正确,wekempf的立场是WaitOne()函数提供了一个隐式内存屏障来解决问题。但这对我来说似乎不够。主线程和后台线程可以在两个单独的处理器上执行。因此,如果Set()也没有提供隐式内存屏障,那么Task()函数最终可能会在其中一个处理器上执行(即使使用volatile变量):
private void Task()
{
// possibly long-running work goes here
_completedSignal.Set();
_a = result1;
_b = result2;
_c = result3;
_completed = true;
}
I have searched high and low for information regarding memory barriers and the EventWaitHandles, and I have come up with nothing. The only reference I have seen is the one wekempf has made to Jeffrey Richter's book. The problem I have with this is that the EventWaitHandle is meant to synchronize threads, not access to data. I have never seen any example where EventWaitHandle (e.g., ManualResetEvent) is used to synchronize access to data. As such, I'm hard-pressed to believe that EventWaitHandle does anything with regard to memory barriers. Otherwise, I would expect to find some reference to this on the internet.
我已经搜索了有关内存障碍和EventWaitHandles的信息,我已经找不到了。我见过的唯一一个参考是wekempf对杰弗里里希特的书所做的。我遇到的问题是EventWaitHandle用于同步线程,而不是访问数据。我从未见过使用EventWaitHandle(例如,ManualResetEvent)来同步数据访问的任何示例。因此,我很难相信EventWaitHandle会对内存障碍做任何事情。否则,我希望在互联网上找到一些参考。
EDIT #2: This is a response to wekempf's response to my response... ;)
编辑#2:这是对wekempf对我的回复的回应的回应......;)
I managed to read the section from Jeffrey Richter's book at amazon.com. From page 628 (wekempf quotes this too):
我设法阅读了Jeffrey Richter在amazon.com上的书中的部分。从第628页开始(wekempf引用了这个):
Finally, i should point out that whenever a thread calls an interlocked method, the CPU forces cache coherency. So if you are manipulating variables via interlocked methods, you do not have to worry about all of this memory model stuff. Furthermore, all thread synchronization locks (Monitor, ReaderWriterLock, Mutex, Semaphone, AutoResetEvent, ManualResetEvent, etc.) call interlocked methods internally.
最后,我应该指出,每当一个线程调用一个互锁方法时,CPU就会强制缓存一致性。因此,如果您通过互锁方法操作变量,则不必担心所有这些内存模型的内容。此外,所有线程同步锁(Monitor,ReaderWriterLock,Mutex,Semaphone,AutoResetEvent,ManualResetEvent等)都在内部调用互锁方法。
So it would seem that, as wekempf pointed out, that the result variables do not require the volatile keyword in the example as shown since the ManualResetEvent ensures cache coherency.
因此,正如wekempf指出的那样,似乎结果变量不需要示例中的volatile关键字,因为ManualResetEvent确保了缓存一致性。
Before closing this edit, there are two additional points I'd like to make.
在关闭此编辑之前,还有两点我想做。
First, my initial assumption was that the background thread would potentially run multiple times. I obviously overlooked the name of the class (OneUseBackgroundOp)! Given that it is only run once, it is not clear to me why the DoSomething() function calls WaitOne() in the manner that it does. What is the point of waiting initialWaitMs milliseconds if the background thread may or may not be done at the time DoSomething() returns? Why not just kickoff the background thread and use a lock to synchronize access to the results variables OR simply execute the contents of the Task() function as part of the thread that calls DoSomething()? Is there a reason not to do this?
首先,我最初的假设是后台线程可能会多次运行。我显然忽略了班级的名字(OneUseBackgroundOp)!鉴于它只运行一次,我不清楚为什么DoSomething()函数以它的方式调用WaitOne()。如果在DoSomething()返回时可能会或可能不会执行后台线程,那么等待initialWaitMs毫秒的重点是什么?为什么不启动后台线程并使用锁来同步对结果变量的访问,或者只是执行Task()函数的内容作为调用DoSomething()的线程的一部分?有没有理由不这样做?
Second, it seems to me that not using some kind of locking mechanism on the results variables is still a bad approach. True, it is not needed in the code as shown. But at some point down the road, another thread may come along and try to access the data. It would be better in my mind to prepare for this possibility now rather than try to track down mysterious behavior anomalies later.
其次,在我看来,在结果变量上不使用某种锁定机制仍然是一种不好的方法。没错,代码中不需要它,如图所示。但在某些时候,另一个线程可能会出现并尝试访问数据。在我看来,现在更好地为这种可能性做准备,而不是试图追踪以后的神秘行为异常。
Thanks to everyone for bearing with me on this. I've certainly learned a lot by participating in this discussion.
感谢大家对我的关注。通过参与这次讨论,我确实学到了很多东西。
#3
3
Wait functions have an implicit memory barrier. See http://msdn.microsoft.com/en-us/library/ms686355(v=vs.85).aspx
等待函数具有隐式内存屏障。请参阅http://msdn.microsoft.com/en-us/library/ms686355(v=vs.85).aspx
#4
1
First, I'm not sure if I should "Answer my own question" or use a comment for this, but here goes:
首先,我不确定我是否应该“回答我自己的问题”或对此进行评论,但这里是:
My understanding is that volatile prevents code/memory optimizations from moving the accesses to my result variables (and the completed boolean) such that the the thread that reads the result will see upt-to-date data.
我的理解是volatile会阻止代码/内存优化将访问移动到我的结果变量(和完成的布尔值),以便读取结果的线程将看到最新的数据。
You wouldn't want the _completed boolean made visible to all threads after the Set() due to compiler or emmpry optimaztions/reordering. Likewise, you wouldn't want the writes to the results _a, _b, _c being seen after the Set().
由于编译器或emmpry optimaztions / reordering,你不希望在Set()之后使所有线程看到_completed布尔值。同样,您不希望在Set()之后看到对结果_a,_b,_c的写入。
EDIT: Further explaination/clarification on the question, in regards to items mentioned by Matt Davis:
编辑:关于问题的进一步解释/澄清,关于马特戴维斯提到的项目:
Finally, i should point out that whenever a thread calls an interlocked method, the CPU forces cache coherency. So if you are manipulating variables via interlocked methods, you do not have to worry about all of this memory model stuff. Furthermore, all thread synchronization locks (Monitor, ReaderWriterLock, Mutex, Semaphone, AutoResetEvent, ManualResetEvent, etc.) call interlocked methods internally.
最后,我应该指出,每当一个线程调用一个互锁方法时,CPU就会强制缓存一致性。因此,如果您通过互锁方法操作变量,则不必担心所有这些内存模型的内容。此外,所有线程同步锁(Monitor,ReaderWriterLock,Mutex,Semaphone,AutoResetEvent,ManualResetEvent等)都在内部调用互锁方法。
So it would seem that, as wekempf pointed out, that the result variables do not require the volatile keyword in the example as shown since the ManualResetEvent ensures cache coherency.
因此,正如wekempf指出的那样,似乎结果变量不需要示例中的volatile关键字,因为ManualResetEvent确保了缓存一致性。
So you are both in agreement that such an operation takes care of caching between processors or in registers etc.
所以你们都同意这样的操作会处理处理器之间或寄存器等的缓存。
But does it prevent reording to guarantee such that BOTH the results are assigned before the completed flag, and that the completed flag is assigned true before the ManualResetEvent is Set?
但它是否会阻止重新保证,以便在完成标志之前分配结果,并且在设置ManualResetEvent之前将完成标志分配为true?
First, my initial assumption was that the background thread would potentially run multiple times. I obviously overlooked the name of the class (OneUseBackgroundOp)! Given that it is only run once, it is not clear to me why the DoSomething() function calls WaitOne() in the manner that it does. What is the point of waiting initialWaitMs milliseconds if the background thread may or may not be done at the time DoSomething() returns? Why not just kickoff the background thread and use a lock to synchronize access to the results variables OR simply execute the contents of the Task() function as part of the thread that calls DoSomething()? Is there a reason not to do this?
首先,我最初的假设是后台线程可能会多次运行。我显然忽略了班级的名字(OneUseBackgroundOp)!鉴于它只运行一次,我不清楚为什么DoSomething()函数以它的方式调用WaitOne()。如果在DoSomething()返回时可能会或可能不会执行后台线程,那么等待initialWaitMs毫秒的重点是什么?为什么不启动后台线程并使用锁来同步对结果变量的访问,或者只是执行Task()函数的内容作为调用DoSomething()的线程的一部分?有没有理由不这样做?
The concept of the sample is to execute a possibly long-runnig task. If the task can be completed within an exceptable amount of time, then the calling thread will get access to the result and continue with normal processing. But sometime a task can take quite a long time and the claiing thread cannot be blocked for that period and can take reasonable steps to deal with that. That can include checking back later on the operation using the Completed property.
该示例的概念是执行可能长期运行的任务。如果任务可以在一段不可及的时间内完成,那么调用线程将获得对结果的访问权并继续正常处理。但是有时某项任务可能需要很长时间才能完成,并且在此期间无法阻止攻击线程,并且可以采取合理的步骤来解决这个问题。这可以包括稍后使用Completed属性检查操作。
A concrete example: A DNS resolve is often very quick (subsecond) and worth waiting for even from a GUI, but sometimes it can take many many seconds. So by using a utility class like the sample, one could gets a result easily from the point-of-view of the caller 95% of the time and not lock up the GUI the other 5%. One could use a Background worker, but that can be overkill for an operation that the vast majority of the time doesn't need all that plumbing.
一个具体的例子:DNS解析通常非常快(亚秒)并且值得等待甚至从GUI,但有时它可能需要很多秒。因此,通过使用类似于示例的实用程序类,可以在95%的时间内从调用者的角度轻松获得结果,而不是将GUI锁定为另外5%。可以使用背景工作者,但对于绝大多数时间不需要所有管道的操作来说,这可能是过度的。
Second, it seems to me that not using some kind of locking mechanism on the results variables is still a bad approach. True, it is not needed in the code as shown.
其次,在我看来,在结果变量上不使用某种锁定机制仍然是一种不好的方法。没错,代码中不需要它,如图所示。
The result (and completed flag) data is meant to be write-once, read-many. If I added a lock to assign the results and flag, I'd also have to lock in my result getters, and I never liked seeing getters lock just to return a data point. From my reading, such fine-grained locking is not appropriate. If an operation has 5 or 6 results, the caller has to take and release the lock 5 or 6 times needlessly.
结果(和完成标志)数据意味着一次写入,多次读取。如果我添加了一个锁以分配结果和标志,我还必须锁定我的结果getter,而且我从不喜欢看到getter锁只是为了返回一个数据点。从我的阅读来看,这种细粒度的锁定是不合适的。如果操作有5或6个结果,则调用者必须不必要地取出和释放锁5或6次。
But at some point down the road, another thread may come along and try to access the data. It would be better in my mind to prepare for this possibility now rather than try to track down mysterious behavior anomalies later.
但在某些时候,另一个线程可能会出现并尝试访问数据。在我看来,现在更好地为这种可能性做准备,而不是试图追踪以后的神秘行为异常。
Because I have a volatile completed flag that is guarenteed to be set before the volatile results are, and the only access to the results is through the getters, and as mentioned in the smaple, an exception is thrown if the getter is called and the operation is not yet complete, I'd expect that the Completed and result getters CAN be invoked by a thread other than the one that called DoSomething(). That's my hope anyway. I believe this to be true with the volatiles anyway.
因为我有一个易失性的完成标志,在挥发性结果之前被设置为保证,并且对结果的唯一访问是通过getter,并且如在smaple中所提到的,如果调用getter并且操作,则抛出异常尚未完成,我希望可以通过调用DoSomething()之外的线程调用Completed和result getter。无论如何,这是我的希望。无论如何,我相信这对挥发物来说也是如此。
#5
0
Based on what you've shown, I would say that, no, the volatiles
are not required in that code.
根据你所展示的内容,我会说,不,该代码中不需要挥发物。
The ManualResetEvent
itself doesn't have an implicit memory barrier. However, the fact that the main thread is waiting for the signal means that it can't modify any variables. At least, it can't modify any variables while it's waiting. So I guess you could say that waiting on a synchronization object is an implicit memory barrier.
ManualResetEvent本身没有隐式内存屏障。但是,主线程正在等待信号这一事实意味着它无法修改任何变量。至少,它在等待时无法修改任何变量。所以我想你可以说等待同步对象是一个隐含的内存障碍。
Note, however, that other threads, if they exist and have access to those variables, could modify them.
但请注意,其他线程(如果存在并且可以访问这些变量)可以修改它们。
From your question, it seems that you're missing the point of what volatile
does. All volatile
does is tell the compiler that the variable might be modified by other threads asynchronously, so it shouldn't optimize code that accesses the variable. volatile
does not in any way synchronize access to the variable.
从你的问题来看,似乎你忽略了挥发性的作用。所有volatile都告诉编译器该变量可能被其他线程异步修改,因此它不应该优化访问该变量的代码。 volatile不会以任何方式同步对变量的访问。