我想知道这项任务是否有更好的解决方案。一个函数由一些线程同时调用,但如果某个线程已经在执行代码,则其他线程应跳过该部分代码并等待该线程完成执行。这就是我现在所拥有的:
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();
}
}
我想要实现的是更快的执行速度,更低的开销和更漂亮的外观。
答案 0 :(得分:0)
您可以使用Barrier
和AutoResetEvent
的组合来执行此操作。
您可以使用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
}
}