在C#中插入/递减指针的线程安全方式

时间:2017-09-12 14:05:39

标签: c# pointers thread-safety clr

奇妙的Interlocked类提供了Increment的重载:

Interlocked.Increment(ref int loc); 

这看起来非常接近我正在寻找的东西。只有,我所拥有的不是变量本身,而是指针。所以我不能使用ref但是需要重载:

// does not exist: 
Interlocked.Increment(int* loc); 

有解决方法吗? 有效和线程安全的任何其他方式通过其在C#中的地址递增值?

2 个答案:

答案 0 :(得分:1)

所以我们可以轻松地做window.addEventListener('load', preload); var canvas = document.getElementById('myCanvas'); var stage, queue; var ball; var paddle; var settings = { lives: 3, points: 0, speed: 3, ballMovingSpeed: 7 } var fingerDown = false; var keys = { left: false, right: false, shoot: false } var ballSettings = { ballRadius: 10, dx: 2, dy: -2 } var canvasWidth = canvas.width; var canvasHeight = canvas.height; function preload() { "use strict"; stage = new createjs.Stage("myCanvas"); queue = new createjs.LoadQueue(true); createjs.Ticker.setFPS(60); createjs.Ticker.addEventListener("tick", tickHappened); drawPaddle(); drawBall(); window.addEventListener('keydown', keyDown); window.addEventListener('keyup', keyUp); } function drawBall() { "use strict" ball = new createjs.Shape(); ball.graphics.beginFill('red').drawCircle(0, 0, 10); stage.addChild(ball); ball.x = 400; ball.y = 535; canvasHeight += ballSettings.dy; canvasWidth += ballSettings.dx; } // paddle Movement //--------------------------------* function keyDown(e) { "use strict"; console.log(e.keyCode); switch (e.keyCode) { case 37: keys.left = true; break; case 39: keys.right = true; break; case 32: keys.shoot = true; break; } } function keyUp(e) { "use strict"; switch (e.keyCode) { case 37: keys.left = false; break; case 39: keys.right = false; break; case 32: keys.shoot = false; break; } } function movePaddle() { "use strict"; if (keys.left) { paddle.x -= settings.speed; if (paddle.x < 0 + paddle.regX) { paddle.x = 0 + paddle.regX; } } else if (keys.right) { paddle.x += settings.speed; if (paddle.x > canvasWidth - paddle.width + paddle.regX) { paddle.x = canvasWidth - paddle.width + paddle.regX; } } else if (keys.shoot) { console.log("shoot ball"); if (canvasWidth + ballSettings.dx > canvas.width - ballSettings.ballRadius || canvasWidth + ballSettings.dx < ballSettings.ballRadius) { ballSettings.dx = -ballSettings.dx; } if (canvasHeight + ballSettings.dy > canvas.height - ballSettings.ballRadius || canvasHeight + ballSettings.dy < ballSettings.ballRadius) { ballSettings.dy = -ballSettings.dy; } } } function tickHappened(e) { "use strict"; movePaddle(); stage.update(e); } ;相应的本机函数看起来可写:

internal static extern Increment(ref int* loc);

但你不能使用它。生成的代码如下所示:

int *Increment(int **loc)
{
    return (int*)InterlockedAdd((int *)loc, sizeof(int));
}

但这实际上是未定义的。如果loc增加了太多次,它可能会溢出,并且locus最终会指向一个非常低的地址。 (base可能就在用户内存的顶部......)。

另一方面,如果你有一个指向要增加的整数的指针;只是P /已经调用了int *locus = Interlocked.Increment(loc); if (locus < base + length) { // do something with locus } 。哎呀;不是64位;必须建立一个小的C dll来获取内在的。

答案 1 :(得分:0)

编辑:这个问题的实际答案要简单得多。假设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.Decrement)很重要,因为JIT会为它生成有效的代码。 (但效率不如直接使用ref local,但......)

这部分答案现已过时。但是,我把它留在这里,因为将本地包装到struct的技术可能会有所帮助......

只有在您可以选择修改代码和“指针”的语义时,此答案才适用。确切地说,它不回答原始问题,但应该是有用的。

除了P / Invoke本机函数的选项(及其缺点:P / Invoke开销,非托管函数维护开销)之外,还有另一种选择。它可以直接使用Interlocked类。 JIT编译器将为它生成更高效的代码,并保存P / Invoke调用。

该解决方案受到这个主题的启发,实际上是Mike Danes提供的答案的无耻副本:

https://social.msdn.microsoft.com/Forums/en-US/b9f9e359-3ca9-43c1-9870-fe562794fca8/interlockedcompareexchange-of-unmanaged-memory-from-vb?forum=clr&prof=required

以下代码演示了如何从多个线程原子地减少本地堆栈分配的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*指针本身。为了原子地减少struct内部的值,我们取消引用指针来调用struct的辅助函数Decrement()。这只是在包装的值上调用Interlocked.Decrement