C# C中指针入/减的线程安全方法#
奇妙的联锁类提供了这个超负荷的增量:C# C中指针入/减的线程安全方法#,c#,pointers,thread-safety,clr,C#,Pointers,Thread Safety,Clr,奇妙的联锁类提供了这个超负荷的增量: Interlocked.Increment(ref int loc); 这看起来真的很接近我要找的。只是,我所拥有的不是变量本身,而是指向它的指针。因此,我不能使用ref,但需要重载: // does not exist: Interlocked.Increment(int* loc); 有解决办法吗?通过C#中的地址高效且线程安全地增加值的任何其他方法 所以我们可以做内部静态外部增量(ref int*loc)足够容易;相应的本机函数看起来是可写的
Interlocked.Increment(ref int loc);
这看起来真的很接近我要找的。只是,我所拥有的不是变量本身,而是指向它的指针。因此,我不能使用ref
,但需要重载:
// does not exist:
Interlocked.Increment(int* loc);
有解决办法吗?通过C#中的地址高效且线程安全地增加值的任何其他方法 所以我们可以做
内部静态外部增量(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
}
int*轨迹=联锁增量(loc);
if(轨迹<基准+长度)
{
//做点什么
}
但这实际上是未定义的。若loc增加太多次,它可能会溢出,轨迹最终指向一个非常低的地址。(base可能位于用户内存的顶部…)
另一方面,如果您有一个指向要递增的整数的指针;只需P/调用对InterlockedIncrement
的调用即可。哎呀;不在64位上;必须构建一个很小的C dll来获取其内在特性。编辑:问题的实际答案要简单得多。假设p
是要递减的变量的int*
。至少C#编译器允许对解引用的p直接执行ref
:
Threading.Interlocked.Decrement(ref (*p));
从哪里来
int local = 10;
IntPtr p = (IntPtr)(&local);
...
// decrement local via p
Threading.Interlocked.Decrement(ref (*(int*)p));
请注意,使用interlocked类(interlocked.Decrement
)很重要,因为JIT为其生成有效的代码。(但效率不如直接使用ref local
)
答案的这一部分现在已经过时了。但是,我把它留在这里,因为将本地文件包装到结构中的技术无论如何都可能有帮助
仅当您可以选择修改代码和“指针”的语义时,此答案才适用。确切地说,它并没有回答最初的问题,但应该是有帮助的
除了p/Invoke本机函数的选项(及其缺点:p/Invoke开销、非托管函数维护开销)之外,还有另一个选项。它允许直接使用联锁
类。JIT编译器将为它生成更高效的代码,我们将保存P/Invoke调用
该解决方案的灵感来源于以下线索,实际上是答案的无耻副本:
下面的代码演示如何从多个线程原子地递减本地堆栈分配的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值;
公共下行计数器(int initialValue){
值=初始值;
}
公共整数减量{
返回联锁减量(参考值);
}
}
不安全类程序{
静态void Main(字符串[]参数){
//本地堆栈分配的计数器,由所有线程共享
下行计数器=新下行计数器(10);
//有些工作线程
行动工作=(c)=>{
双a=1;
对于(int i=0;i<1)您是否考虑过使用它来代替您自己的同步程序?这听起来真的像是更新您的问题,解释您正在做什么,您需要编写一个没有分配的同步程序,可能有更好的解决方案。您无法运行(real)NET应用程序没有一个新的,那么问题出在哪里呢?您只需要为所有线程创建一个同步对象。一个信号量或一个倒计时事件。因此,您愿意承担主线程在该变量上浪费数千个周期的成本,但不愿意承担cos分配一个通常不需要数千个周期的小对象的方法?@HaymoKutschbach祝你在.NET应用程序中好运。:)关闭线程之间共享的单个int不会导致多个分配(如果变量被提升到静态字段中,则不会导致任何分配)因此,不清楚您为什么要避免这种情况?您是否查看了生成的IL以了解闭包是如何生成的?PInvoke听起来很有希望。我这样做了,它正在32位上工作。不过,我仍在与64位进行斗争。没有这样的导出“InterlocatedDecrement”或“InterlocatedExchangeAdd”(这似乎是Intelock使用的。真的是递减)在64位kernel32.dll中。谷歌说这始终是作为内在的。不确定这在64位.NET中是如何工作的。可能是一些CLR欺骗?@HaymoKutschbach:他们必须有一个本机函数来完成它。这里有一个关于kernel32.dll的相关问题。除非还有另一个InterlocatedDecrement
留给comp某些dll中的不适应性(?)对于64位而言,@Joshua建议的小型非托管C辅助dll似乎是一条可行之路。人们可能会决定保存该dll,并使用带有x64汇编指令的byte[]数组在运行时构造委托(非托管函数指针)(我不会建议这样冒险的尝试…)