【问题标题】:Should I use Interlocked.Exchange here or is a standard write sufficient?我应该在这里使用 Interlocked.Exchange 还是标准写入就足够了?
【发布时间】:2015-01-29 14:14:47
【问题描述】:

以下方法尝试获取与其他线程共享的锁。如果在某个时间段内可以获得锁,就会执行一些Action。

如果在指定时间段内无法获得锁,我想通过生成一个任务在单独的线程上再次尝试整个过程。我不希望我的初始线程等待获得锁,因为在这种情况下性能很重要。但是,在此任务完成之前,我不希望再创建任何任务。换句话说,在任何给定时间运行的任务都不应该超过一个。不允许在任务仍在运行时对“ExecuteOrOffload”进行任何后续调用来创建另一个任务。

我想出了以下解决方案,我想知道我的设置方法 (setFlag) 是否应该使用 Interlocked.Exchange 来更改标志值,或者它是否有任何区别?代码在具有大量内核的机器上运行。

    void Main()
    {
        //I currently use this
        ExecuteOrOffload(_theLock, () => { }, () => _theFlag, newVal => _theFlag = newVal, 0);

        //Should I use this instead? i.e. - Does my setter need to use Interlocked.Exchange? 
        ExecuteOrOffload(_theLock, () => { }, () => _theFlag, newVal => Interlocked.Exchange(ref _theFlag, newVal));
    }

    private object _theLock = new Object();
    private int _theFlag = 0;


    //Flag = 0. Allow a new Task to start if needed
    //Flag = 1. A Task is already running. Don't allow new tasks to be created
    private void ExecuteOrOffload(object thelock, Action theAction, Func<int> getFlag, Action<int> setFlag, int timeout = 0)
    {
       bool acquiredLock = false;

       try
       {
        //If the lock can be obtained, execute the Action
        Monitor.TryEnter(thelock, TimeSpan.FromSeconds(timeout), ref acquiredLock);
        if (acquiredLock)
        {
            theAction();
        }
        //The lock was not obtained. Either offload to a new task or do nothing
        else
        {
            //Get the current value of the flag
            var currentValue = getFlag();
            //Try set the flag to 1. Only one thread should manage to do this.
            var originalValue = Interlocked.CompareExchange(ref currentValue, 1, 0); 

            //If this thread didn't change the flag then just return.
            if (originalValue == currentValue)
            {
                return;
            }

            //The thread that gets here changes the actual flag value
            setFlag(1);
            //...and starts a new task
            Task.Factory.StartNew(() =>
            {
                try
                {
                    //Retry the whole process from a Task. This time the flag is set to 1 and a new Task will not be created should the lock time out
                    ExecuteOrOffload(thelock, theAction, getFlag, setFlag, 2);
                }
                finally
                {
                    //Task completed (either timed out or executed the action, we don't care which). Reset the flag to allow future calls to start a new Task
                    setFlag(0);
                }
            });
        }
    }
    catch (Exception e)
    {
        //Log exception
    }
    finally
    {
        //If this thread took the lock, release it
        if (acquiredLock)
        {
            Monitor.Exit(thelock);
        }
    }
}

【问题讨论】:

    标签: c# multithreading task interlocked


    【解决方案1】:

    get/setFlag 模式完全是活泼的。这是不安全的。您没有对共享变量进行任何同步。

    你正在做的Interlocked.CompareExchange 是在一个非共享的局部变量上。那从来没有帮助。我们没有的智能 JIT 只会优化这个联锁操作(或将其转换为栅栏)。

    对共享变量执行Interlocked.CompareExchange。在这种情况下,没有单独的商店,您的问题就解决了。

    【讨论】:

    • 你的意思是我应该删除 'currentValue = getFlag()' 并只用 '_theFlag' 替换它吗?我使用 getter/setter 而不是直接访问 _theFlag 是有原因的(这会更简单)。有问题的类有两个标志,用于不同的目的。所以我需要知道根据谁调用该方法来使用两个标志中的哪一个。这留下了两个选择 - 我可以将正确的标志传递给方法(通过引用,因此是 getter/setter),或者我可以完全复制该方法并让每个方法使用正确的标志。
    • 您可以使用ref myFlag 传递正确的标志。或者,在标志周围使用包装类。包装器会暴露一个字段。
    • 我已经尝试了这两种解决方案,但你不能通过引用传递给任务。此外,使用包装器(如具有 int 属性的标志对象)仍然不允许您在不进行本地复制的情况下更改值)。我正在努力想出一种方法来做到这一点。理想情况下,我不必复制/粘贴整个方法,只需访问每个方法中的正确标志。一定有办法做到这一点。
    • 您可以引用一个字段。类 c { int 字段; }。 ref new c().field.
    • 太棒了 - 正好解决了我的问题。谢谢你的帮助。我从中学到了很多。
    猜你喜欢
    • 2014-12-07
    • 1970-01-01
    • 2021-12-03
    • 1970-01-01
    • 2018-11-03
    • 1970-01-01
    • 2014-05-26
    • 1970-01-01
    • 2011-05-06
    相关资源
    最近更新 更多