我在MS文档中读到,在32位Intel计算机上分配64位值不是原子操作;也就是说,操作不是线程安全的。这意味着如果两个人同时为静态Int64
字段分配值,则无法预测该字段的最终值。
三部分问题:
Int64
作业吗?答案 0 :(得分:18)
这与您遇到的每个变量无关。如果某个变量用作共享状态或某些内容(包括但不限于某些 static
字段),则应该处理此问题。对于由于在闭包或迭代器转换中被关闭而未被提升的局部变量而言,它完全没有问题,并且一次由单个函数(因此,单个线程)使用。
答案 1 :(得分:12)
即使写入是原子,也可能在访问变量时仍需要锁定。如果你不这样做,你至少必须使变量volatile
确保所有线程在下次读取变量时看到新值(这几乎总是你想要的)。这可以让你做原子的,易变的集合 - 但是只要你想做更有趣的事情,比如给它添加5,你就会回到锁定状态。
锁定免费编程非常非常难以正确。您需要知道完全您正在做什么,并将复杂性保持为尽可能小的代码。就个人而言,除了非常着名的模式之外,我甚至很少尝试尝试它,例如使用静态初始化程序初始化集合,然后从集合中读取而不锁定。
使用Interlocked
类可以在某些情况下提供帮助,但是取出锁定几乎总是容易得多。无可争议的锁是“非常便宜的”(不可否认,它们随着更多内核而变得昂贵,但所有内容也是如此) - 在你有充分的证据证明它实际上会产生重大影响之前,不要乱用无锁代码。 / p>
答案 2 :(得分:7)
MSDN:
分配此类型的实例是 在所有硬件上都没有线程安全 平台因为二进制 该实例的表示可能 太大而无法分配到一个单一的 原子操作。
但是:
与任何其他类型一样,阅读和 写一个共享变量 包含此类型的实例必须 受到锁保护 线程安全。
答案 3 :(得分:2)
如果你有一个共享变量(比如,作为一个类的静态字段,或者作为一个共享对象的字段),并且该字段或对象将被用于跨线程,那么,是的,你需要确保通过原子操作保护对该变量的访问。 x86处理器具有内在函数以确保发生这种情况,并且此工具通过System.Threading.Interlocked类方法公开。
例如:
class Program
{
public static Int64 UnsafeSharedData;
public static Int64 SafeSharedData;
static void Main(string[] args)
{
Action<Int32> unsafeAdd = i => { UnsafeSharedData += i; };
Action<Int32> unsafeSubtract = i => { UnsafeSharedData -= i; };
Action<Int32> safeAdd = i => Interlocked.Add(ref SafeSharedData, i);
Action<Int32> safeSubtract = i => Interlocked.Add(ref SafeSharedData, -i);
WaitHandle[] waitHandles = new[] { new ManualResetEvent(false),
new ManualResetEvent(false),
new ManualResetEvent(false),
new ManualResetEvent(false)};
Action<Action<Int32>, Object> compute = (a, e) =>
{
for (Int32 i = 1; i <= 1000000; i++)
{
a(i);
Thread.Sleep(0);
}
((ManualResetEvent) e).Set();
};
ThreadPool.QueueUserWorkItem(o => compute(unsafeAdd, o), waitHandles[0]);
ThreadPool.QueueUserWorkItem(o => compute(unsafeSubtract, o), waitHandles[1]);
ThreadPool.QueueUserWorkItem(o => compute(safeAdd, o), waitHandles[2]);
ThreadPool.QueueUserWorkItem(o => compute(safeSubtract, o), waitHandles[3]);
WaitHandle.WaitAll(waitHandles);
Debug.WriteLine("Unsafe: " + UnsafeSharedData);
Debug.WriteLine("Safe: " + SafeSharedData);
}
}
结果:
不安全: - 24050275641 安全:0
有趣的是,我在Vista 64上以x64模式运行它。这表明64位字段被运行时视为32位字段,即64位操作是非原子的。有人知道这是CLR问题还是x64问题?
答案 4 :(得分:1)
在32位x86平台上,最大的原子大小的内存是32位。
这意味着如果某些内容写入或读取64位大小的变量,则该读/写操作可能会在执行期间被抢占。
这只是一个可能的竞争条件,在32位平台上进行64位分配。
然而,即使使用32位变量,也可能存在读写条件,因此任何共享变量都应以某种方式同步以解决这些竞争条件。
答案 5 :(得分:0)
这是真的吗?是的,事实证明。如果您的寄存器中只有32位,并且您需要将64位值存储到某个存储器位置,则需要进行两次加载操作和两次存储操作。如果您的进程被这两个加载/存储之间的另一个进程中断,则另一个进程可能会损坏您的一半数据!奇怪但真实。这在每个构建的处理器上都存在问题 - 如果您的数据类型比寄存器长,则会出现并发问题。
这是我在现实世界中会担心的吗?是的,不是。由于几乎所有现代编程都有自己的地址空间,因此如果您正在进行多线程编程,则只需要担心这一点。
如果我的应用程序是多线程的,我真的需要用锁定代码包围我的所有Int64分配吗?可悲的是,是的,如果你想获得技术支持。实际上,在较大的代码块周围使用Mutex或Semaphore比在全局可访问的变量上锁定每个单独的set语句通常更容易。