在仅包含该语句的方法中用简单赋值替换 Volatile.Write() 是否安全?
Is it safe to replace Volatile.Write() with simple assignment in a method that contains only that statement?
这是一道 C# 多线程高级题。
假设我有这段代码用作锁定机制,只允许一个线程启动某些操作:
private static int _guard = 0;
private static bool acquire() {
return Interlocked.CompareExchange(ref _guard, 1, 0) == 0;
}
private static void release() {
Volatile.Write(ref _guard, 0);
}
此锁用于保护当时只能由一个线程执行的方法:
public readonly Status Status = new (); // updated from thread that runs someTask
void TryRunningTask {
if (acquire()) {
return await someTask();
} else {
InfoMessage = "Another user is currently running someTask.";
}
}
我的问题是,如果我将 release()
更改如下:
private static void release() {
_guard = 0;
}
程序的行为是否仍然完全相同?那会破坏线程安全吗?这种变化有意义吗?
我提出此更改的原因如下:
release()
方法中没有其他 read/write 操作。
在 Volatile.Write method 的 MS 文档中,它说:
Writes a value to a field. On systems that require it, inserts a
memory barrier that prevents the processor from reordering memory
operations as follows: If a read or write appears before this method
in the code, the processor cannot move it after this method.
所以因为我的 release()
方法没有其他操作 before/after Volatile.Write()
调用我想我可以用简单的赋值语句替换它 _guard = 0;
对吗?
- 根据 C# Standard 第 10.6 节,
_guard = 0;
操作保证是原子操作:
10.6 Atomicity of variable references
Reads and writes of the following data types shall be atomic: bool, char, byte, sbyte, short,
ushort, uint, int, float, and reference types.
不,您不能删除对 Volatile.Write
的调用。
关于原子性点,您是正确的:C# 和 CLR 要求 32 位和更小的数据类型应该是原子的。
然而,要考虑的不仅仅是原子性。还需要考虑指令重新排序和处理器缓存。
重新排序可能由 CLR 抖动引起,并且您的函数的大小无关紧要,因为它可能被内联到调用它的任何函数中(并且可能会被认为是短的)。
处理器也可以重新排序指令,如果它符合给定的指令。
所以这个需要一个内存屏障thread-safe。
处理器缓存是另一个问题:如果处理器内核没有被告知读取或写入是易变的,它可能只使用自己的缓存而忽略其他内核缓存中的内容。
但是,Volatile.Write
可能还不够。我无法从您所展示的内容中准确判断,但您似乎有多个线程读取 和 写入。因此,我认为您应该改用 Interlocked.Exchange
。
这是一道 C# 多线程高级题。
假设我有这段代码用作锁定机制,只允许一个线程启动某些操作:
private static int _guard = 0;
private static bool acquire() {
return Interlocked.CompareExchange(ref _guard, 1, 0) == 0;
}
private static void release() {
Volatile.Write(ref _guard, 0);
}
此锁用于保护当时只能由一个线程执行的方法:
public readonly Status Status = new (); // updated from thread that runs someTask
void TryRunningTask {
if (acquire()) {
return await someTask();
} else {
InfoMessage = "Another user is currently running someTask.";
}
}
我的问题是,如果我将 release()
更改如下:
private static void release() {
_guard = 0;
}
程序的行为是否仍然完全相同?那会破坏线程安全吗?这种变化有意义吗?
我提出此更改的原因如下:
release()
方法中没有其他 read/write 操作。 在 Volatile.Write method 的 MS 文档中,它说:
Writes a value to a field. On systems that require it, inserts a memory barrier that prevents the processor from reordering memory operations as follows: If a read or write appears before this method in the code, the processor cannot move it after this method.
所以因为我的 release()
方法没有其他操作 before/after Volatile.Write()
调用我想我可以用简单的赋值语句替换它 _guard = 0;
对吗?
- 根据 C# Standard 第 10.6 节,
_guard = 0;
操作保证是原子操作:
10.6 Atomicity of variable references
Reads and writes of the following data types shall be atomic: bool, char, byte, sbyte, short, ushort, uint, int, float, and reference types.
不,您不能删除对 Volatile.Write
的调用。
关于原子性点,您是正确的:C# 和 CLR 要求 32 位和更小的数据类型应该是原子的。
然而,要考虑的不仅仅是原子性。还需要考虑指令重新排序和处理器缓存。
重新排序可能由 CLR 抖动引起,并且您的函数的大小无关紧要,因为它可能被内联到调用它的任何函数中(并且可能会被认为是短的)。
处理器也可以重新排序指令,如果它符合给定的指令。
所以这个需要一个内存屏障thread-safe。
处理器缓存是另一个问题:如果处理器内核没有被告知读取或写入是易变的,它可能只使用自己的缓存而忽略其他内核缓存中的内容。
但是,Volatile.Write
可能还不够。我无法从您所展示的内容中准确判断,但您似乎有多个线程读取 和 写入。因此,我认为您应该改用 Interlocked.Exchange
。