更好地同时进行"做或等待并跳过"

时间:2016-11-02 14:25:14

标签: c# .net multithreading concurrency manualresetevent

我想知道这项任务是否有更好的解决方案。一个函数由一些线程同时调用,但如果某个线程已经在执行代码,则其他线程应跳过该部分代码并等待该线程完成执行。这就是我现在所拥有的:

int _flag = 0;
readonly ManualResetEventSlim Mre = new ManualResetEventSlim();

void Foo()
{
    if (Interlocked.CompareExchange(ref _flag, 1, 0) == 0)
    {
        Mre.Reset();
        try
        {
            // do stuff
        }
        finally
        {
            Mre.Set();
            Interlocked.Exchange(ref _flag, 0);
        }
    }
    else
    {
        Mre.Wait();
    }
}

我想要实现的是更快的执行速度,更低的开销和更漂亮的外观。

4 个答案:

答案 0 :(得分:0)

您可以使用BarrierAutoResetEvent的组合来执行此操作。

您可以使用Barrier确保只有一个线程进入“工作”方法。

using System; using System.Threading; using System.Threading.Tasks; namespace Demo { class Program { const int TASK_COUNT = 3; static readonly Barrier barrier = new Barrier(TASK_COUNT); static readonly AutoResetEvent gate = new AutoResetEvent(true); static void Main() { Parallel.Invoke(task, task, task); } static void task() { while (true) { Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " is waiting at the gate."); // This bool is just for test purposes to prevent the same thread from doing the // work every time! bool didWork = false; if (gate.WaitOne(0)) { work(); didWork = true; gate.Set(); } Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " is waiting at the barrier."); barrier.SignalAndWait(); if (didWork) Thread.Sleep(10); // Give a different thread a chance to get past the gate! } } static void work() { Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " is entering work()"); Thread.Sleep(3000); Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " is leaving work()"); } } } 用于确保所有线程一直等到输入“work”方法的线程从其返回。

以下是一些示例代码:

{{1}}

但是,Task Parallel Library可能有更好,更高级别的解决方案。值得一读。

答案 1 :(得分:0)

首先,等待线程不会做任何事情,它们只会等待,并且在它们从事件中获得信号后,它们只是移出方法,因此您应该添加while循环。之后,您可以使用AutoResetEvent而不是手动的,如@MatthewWatson所建议的那样。此外,您可以在循环中考虑SpinWait,这是一个轻量级的解决方案。

其次,为什么使用int,如果bool字段明确属于flag性质呢?

第三,为什么不使用简单的锁定,如@grrrrrrrrrrrrr建议的那样?这正是你在这里做的:强迫其他线程等待一个。如果您的代码在给定时间内只有一个线程write,但多个线程可以read,则可以使用ReaderWriterLockSlim对象进行此类同步。

答案 2 :(得分:0)

我想要实现的是更快的执行速度,更低的开销和更漂亮的外观。

  

执行速度更快

除非你的“Do Stuff”非常快,否则这段代码不应该有任何重大开销。

  

降低开销

同样,Interlocked Exchange,/ CompareExchange的开销非常低,手动重置事件也是如此。

如果您的“Do Stuff”确实快,例如移动链表头,然后你可以旋转:

  看起来更漂亮

与正确的单线程C#代码相比,正确的多线程C#代码很少看起来很漂亮。语言习语还没有出现。

那说:如果你有一个真正的快速操作(“几十个周期”),那么你可以旋转:(虽然不知道你的代码到底在做什么,但我不能如果这是正确的话。)

  if (Interlocked.CompareExchange(ref _flag, 1, 0) == 0)
        {
            try
            {
                // do stuff that is very quick.
            }
            finally
            {
                Interlocked.Exchange(ref _flag, 0);
            }
        }
        else
        {
            SpinWait.SpinUntil(() => _flag == 0);
        }

答案 3 :(得分:-1)

首先想到的是改变它以使用锁。这不会跳过代码,但会导致每个线程在第一个线程执行其内容时暂停。这样,在异常情况下,锁也会自动释放。

object syncer = new object();
void Foo()
{
    lock(syncer)
    {
        //Do stuff
    }
}