in-/decrementing C# 中指针的线程安全方式
Thread-safe way of in-/decrementing a pointer in C#
神奇的 Interlocked
class 提供了 Increment
的重载:
Interlocked.Increment(ref int loc);
这看起来非常接近我要找的东西。只是,我所拥有的不是变量本身,而是指向它的 pointer 。所以我不能使用 ref
但需要这样的重载:
// does not exist:
Interlocked.Increment(int* loc);
有解决办法吗? 高效和线程安全的任何其他方式通过其在 C# 中的地址递增一个值?
所以我们可以internal static extern Increment(ref int* loc);
很容易做到;相应的本机函数看起来可写:
int *Increment(int **loc)
{
return (int*)InterlockedAdd((int *)loc, sizeof(int));
}
但是你不能使用它。您生成的代码类似于:
int *locus = Interlocked.Increment(loc);
if (locus < base + length)
{
// do something with locus
}
但这实际上是未定义的。如果 loc 递增太多次,它可能会溢出并且 locus 最终指向一个非常低的地址。 (基地可能就在用户记忆的顶部......)。
另一方面,如果您有一个指向要递增的整数的指针;已经 P/Invoke 调用了 InterlockedIncrement
。糟糕;不是 64 位;将不得不构建一个小的 C dll 来获取内在的。
编辑:问题的实际答案要简单得多。假设 p
是要递减的变量的 int*
。至少 C# 编译器允许 ref
直接在取消引用的 p 上:
Threading.Interlocked.Decrement(ref (*p));
当来自
int local = 10;
IntPtr p = (IntPtr)(&local);
...
// decrement local via p
Threading.Interlocked.Decrement(ref (*(int*)p));
请注意,使用互锁 class (Interlocked.Decrement
) 很重要,因为 JIT 会为其生成高效代码。 (但不如直接使用 ref local
高效,不过...)
这部分答案现在已经过时了。但是,我把它留在这里,因为将本地包装成 struct
的技术无论如何都可能有帮助...
仅当您可以选择修改 'pointer' 的代码和语义时,此答案才适用。确切地说,它没有回答原始问题,但应该有所帮助。
除了 P/Invoke 本机函数的选项(及其缺点:P/Invoke 开销,非托管函数维护开销)还有另一个选项。它允许直接使用 Interlocked
class。 JIT 编译器将为其生成更高效的代码,我们将节省 P/Invoke 调用。
该解决方案的灵感来自此线程,实际上是 Mike Danes 提供的答案的无耻副本:
下面的代码演示了如何从多个线程中自动递减本地堆栈分配的 int
计数器。它在托管堆上没有任何其他对象并且没有任何更高级的同步对象的情况下消失了。在应用于生产代码之前,请确保确保此方法免受常见风险的影响。
internal struct DownCounter {
internal int value;
public DownCounter(int initialValue) {
value = initialValue;
}
public int Decrement() {
return Interlocked.Decrement(ref value);
}
}
unsafe class Program {
static void Main(string[] args) {
// the local stack allocated counter, to be shared by all threads
DownCounter counter = new DownCounter(10);
// some work for the threads
Action<object> work = (c) => {
double a = 1;
for (int i = 0; i < 1 << 26; i++) {
// do stuff here
a = a % i * a % (i + 1);
}
// decrement the main thread's stack counter
int d = (*((DownCounter*)(IntPtr)c)).Decrement();
Console.WriteLine($"Decremented: {d}");
};
// start 10 threads, each decrements the local, stack allocated(!) counter
for (int i = 0; i < 10; i++) {
Task.Factory.StartNew(work, (IntPtr)(&counter));
}
// poor mans 'spin wait' on the local counter
while (counter.value > 0) {
Thread.Sleep(100);
Console.WriteLine($"# threads running: {counter.value}");
}
Console.Read();
}
}
我们现在不再传递 int*
指针本身,而是将要递减的值封装到一个小结构中,并将指针传递给该结构。为了以原子方式减少结构内的值,我们取消引用指针以调用结构的辅助函数 Decrement()
。这只是对包装值调用 Interlocked.Decrement
。
神奇的 Interlocked
class 提供了 Increment
的重载:
Interlocked.Increment(ref int loc);
这看起来非常接近我要找的东西。只是,我所拥有的不是变量本身,而是指向它的 pointer 。所以我不能使用 ref
但需要这样的重载:
// does not exist:
Interlocked.Increment(int* loc);
有解决办法吗? 高效和线程安全的任何其他方式通过其在 C# 中的地址递增一个值?
所以我们可以internal static extern Increment(ref int* loc);
很容易做到;相应的本机函数看起来可写:
int *Increment(int **loc)
{
return (int*)InterlockedAdd((int *)loc, sizeof(int));
}
但是你不能使用它。您生成的代码类似于:
int *locus = Interlocked.Increment(loc);
if (locus < base + length)
{
// do something with locus
}
但这实际上是未定义的。如果 loc 递增太多次,它可能会溢出并且 locus 最终指向一个非常低的地址。 (基地可能就在用户记忆的顶部......)。
另一方面,如果您有一个指向要递增的整数的指针;已经 P/Invoke 调用了 InterlockedIncrement
。糟糕;不是 64 位;将不得不构建一个小的 C dll 来获取内在的。
编辑:问题的实际答案要简单得多。假设 p
是要递减的变量的 int*
。至少 C# 编译器允许 ref
直接在取消引用的 p 上:
Threading.Interlocked.Decrement(ref (*p));
当来自
int local = 10;
IntPtr p = (IntPtr)(&local);
...
// decrement local via p
Threading.Interlocked.Decrement(ref (*(int*)p));
请注意,使用互锁 class (Interlocked.Decrement
) 很重要,因为 JIT 会为其生成高效代码。 (但不如直接使用 ref local
高效,不过...)
这部分答案现在已经过时了。但是,我把它留在这里,因为将本地包装成 struct
的技术无论如何都可能有帮助...
仅当您可以选择修改 'pointer' 的代码和语义时,此答案才适用。确切地说,它没有回答原始问题,但应该有所帮助。
除了 P/Invoke 本机函数的选项(及其缺点:P/Invoke 开销,非托管函数维护开销)还有另一个选项。它允许直接使用 Interlocked
class。 JIT 编译器将为其生成更高效的代码,我们将节省 P/Invoke 调用。
该解决方案的灵感来自此线程,实际上是 Mike Danes 提供的答案的无耻副本:
下面的代码演示了如何从多个线程中自动递减本地堆栈分配的 int
计数器。它在托管堆上没有任何其他对象并且没有任何更高级的同步对象的情况下消失了。在应用于生产代码之前,请确保确保此方法免受常见风险的影响。
internal struct DownCounter {
internal int value;
public DownCounter(int initialValue) {
value = initialValue;
}
public int Decrement() {
return Interlocked.Decrement(ref value);
}
}
unsafe class Program {
static void Main(string[] args) {
// the local stack allocated counter, to be shared by all threads
DownCounter counter = new DownCounter(10);
// some work for the threads
Action<object> work = (c) => {
double a = 1;
for (int i = 0; i < 1 << 26; i++) {
// do stuff here
a = a % i * a % (i + 1);
}
// decrement the main thread's stack counter
int d = (*((DownCounter*)(IntPtr)c)).Decrement();
Console.WriteLine($"Decremented: {d}");
};
// start 10 threads, each decrements the local, stack allocated(!) counter
for (int i = 0; i < 10; i++) {
Task.Factory.StartNew(work, (IntPtr)(&counter));
}
// poor mans 'spin wait' on the local counter
while (counter.value > 0) {
Thread.Sleep(100);
Console.WriteLine($"# threads running: {counter.value}");
}
Console.Read();
}
}
我们现在不再传递 int*
指针本身,而是将要递减的值封装到一个小结构中,并将指针传递给该结构。为了以原子方式减少结构内的值,我们取消引用指针以调用结构的辅助函数 Decrement()
。这只是对包装值调用 Interlocked.Decrement
。