在这种情况下,我将脏读定义为当内存正被另一个线程写入时从内存中读取。
因此,如果线程#1将100写入线程#2也可以看到的变量,则将50写入同一个变量,其中两个线程在循环中执行此操作。
是否可以读取变量并获得既不是50也不是100的数字? 不使用锁等进行同步。
关于我的设置的更多细节:Intel i3 CPU,我用C#编程。
下面是我的意思的一个例子:
using System;
using System.Collections.Generic;
using System.Threading;
namespace Threading001
{
class Program
{
static void Main(string[] args)
{
long min = long.MinValue;
long max = long.MaxValue;
object number = max;
new Thread(() =>
{
long current2 = (long)number;
if (current2 != min && current2 != max)
{
Console.WriteLine("Unexpected number from thread 2: {0}.", current2);
}
number = min;
}).Start();
while (true)
{
long current = (long)number;
if (current != min && current != max)
{
Console.WriteLine("Unexpected number from thread 1: {0}.", current);
}
number = max;
}
}
}
}
我使数字成为一个对象,因此内存分配在堆而不是堆栈上,以尝试增加内存访问延迟时间。无论如何,通过cpu缓存可能会阻止它。
答案 0 :(得分:1)
你实际上在这里试图依赖几种不同的东西。
首先,存在原子性问题。 ECMA-335指出:
符合标准的CLI应保证对正确对齐的内存的读写访问权限 不大于本机字大小(native int类型的大小)的位置是原子的 (参见§I.12.6.2)当对一个位置的所有写访问都是相同的大小时。原子写入应 除了写的那些之外不要改变任何比特除非明确的布局控制(见 分区II(控制实例布局))用于更改默认行为,数据元素号 大于自然字大小(原生int的大小)应正确对齐。宾语 引用应被视为存储在本机字大小中。
因此,对于一个32位整数,你总是很好 - 对于一个64位整数,如果你在64位CLR上运行你就没问题...假设你的变量是对齐的,他们通常都是。
但是,你也有拳击参与 - 我不会认为这实际上会导致任何问题,但它确实意味着你正在处理多次写入的可见性:一个用于number
变量本身,一个用于框内的数据。有了.NET实现,我相信由于它提供了更强大的内存保证,它仍然是安全的 - 我不想绝对保证它在ECMA内存模型中是安全的。
最后,问题是否是可见,这是超出原子性的。如果线程T1将int
值从0更改为100,则线程T2将其读取,原子性保证T2将看到0或100,从不会出现其他位模式 - 但通常必须存在某种内存屏障涉及保证T2实际上会看到新值而不是陈旧值。这是一个非常复杂的领域 - 如果你想了解更多,我建议你从Joe Duffy's 2007 blog post开始,然后在那里工作。
请注意,min
,max
和number
无论如何都会在堆上,因为它们已经被你的lambda表达式捕获 ...虽然堆栈/堆是一个实现细节。
答案 1 :(得分:0)
它取决于变量是什么(以及处理器等等),但通常是:是的,可以进行脏读。
答案 2 :(得分:0)
我不熟悉特定CPU的详细信息,但一般来说,这取决于READ / WRITE是否为原子,而这又取决于体系结构以及变量的存储方式。
如果变量的大小大于CPU字大小,则可能不是原子。
现代cpu可以保证对对齐的内存地址进行原子访问;如果它没有对未对齐的内存访问没有硬件支持需要更多的考虑。如果未对齐的内存访问由软件处理,则读取或写入不是原子的:一个加载/存储可能导致实际上两个操作。一个例子是PowerPC / Linux,其中内核处理异常处理程序中未对齐的内存访问。
答案 3 :(得分:0)
这是关于线程安全的全部或者它取决于您想要读取的变量的数据类型, 某些类型(如long,int,byte等)是线程安全的,您可以在多个线程中读取和写入它们。
您可以在此处找到更多信息
http://msdn.microsoft.com/en-us/library/dd997305(v=vs.110).aspx