我有一个队列,其中一个线程将执行Enqueue操作,而另一个线程将执行Dequeue。不用说,我必须为它实现一些线程安全性。
我首先尝试在每个Enqueue / Dequeue之前对队列使用锁,因为它可以更好地控制锁定机制。它运作良好,但我的好奇心使我进行了更多测试。
然后我尝试使用Queue.Synchronized包装器保持其他所有内容相同。现在,我不确定它是否真实,但这种方法的性能似乎有点快。
你认为,两者之间的性能确实存在一些差异,或者我只是想象这里的事情......? :)
答案 0 :(得分:17)
在请求Queue.Synchonized
时,您会得到一个SynchronizedQueue
作为回报,该内容在内部队列上使用lock
非常微弱地调用Enqueue
和Dequeue
。因此,性能应与使用Queue
并使用您自己的Enqueue
管理自己对Dequeue
和lock
的锁定相同。
你确实在想象 - 它们应该是一样的。
<强>更新强>
实际上,当使用SynchronizedQueue
时,您正在添加一个间接层,因为您必须通过包装器方法来到达它正在管理的内部队列。如果有任何事情,这会使事情变得非常缓慢,因为你需要为每次调用管理堆栈上的额外帧。上帝知道内衬是否取消了这一点。无论如何 - 它是最小。
更新2
我现在已经对此进行了基准测试,正如我之前的更新所预测的那样:
“Queue.Synchronized”比“Queue + lock”
慢
我进行了单线程测试,因为他们都使用相同的锁定技术(即lock
),因此在“直线”中测试纯粹的开销似乎是合理的。
我的基准测试为发布构建产生了以下结果:
Iterations :10,000,000
Queue+Lock :539.14ms
Queue+Lock :540.55ms
Queue+Lock :539.46ms
Queue+Lock :540.46ms
Queue+Lock :539.75ms
SynchonizedQueue:578.67ms
SynchonizedQueue:585.04ms
SynchonizedQueue:580.22ms
SynchonizedQueue:578.35ms
SynchonizedQueue:578.57ms
使用以下代码:
private readonly object _syncObj = new object();
[Test]
public object measure_queue_locking_performance()
{
const int TestIterations = 5;
const int Iterations = (10 * 1000 * 1000);
Action<string, Action> time = (name, test) =>
{
for (int i = 0; i < TestIterations; i++)
{
TimeSpan elapsed = TimeTest(test, Iterations);
Console.WriteLine("{0}:{1:F2}ms", name, elapsed.TotalMilliseconds);
}
};
object itemOut, itemIn = new object();
Queue queue = new Queue();
Queue syncQueue = Queue.Synchronized(queue);
Action test1 = () =>
{
lock (_syncObj) queue.Enqueue(itemIn);
lock (_syncObj) itemOut = queue.Dequeue();
};
Action test2 = () =>
{
syncQueue.Enqueue(itemIn);
itemOut = syncQueue.Dequeue();
};
Console.WriteLine("Iterations:{0:0,0}\r\n", Iterations);
time("Queue+Lock", test1);
time("SynchonizedQueue", test2);
return itemOut;
}
[SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.GC.Collect")]
private static TimeSpan TimeTest(Action action, int iterations)
{
Action gc = () =>
{
GC.Collect();
GC.WaitForFullGCComplete();
};
Action empty = () => { };
Stopwatch stopwatch1 = Stopwatch.StartNew();
for (int j = 0; j < iterations; j++)
{
empty();
}
TimeSpan loopElapsed = stopwatch1.Elapsed;
gc();
action(); //JIT
action(); //Optimize
Stopwatch stopwatch2 = Stopwatch.StartNew();
for (int j = 0; j < iterations; j++) action();
gc();
TimeSpan testElapsed = stopwatch2.Elapsed;
return (testElapsed - loopElapsed);
}
答案 1 :(得分:6)
我们无法为您解答此问题。只有您可以通过获取探查器并针对应用程序中的实际数据测试两种方案(Queue.Synchronized
与Lock
),自行回答。它甚至可能不是您应用程序中的瓶颈。
那就是说,你应该只使用ConcurrentQueue
。
答案 2 :(得分:0)