什么是等待Semaphore的正确方式?

9
我原以为以下代码会让10个线程运行,每次只有两个线程在跑,当Release()被调用了10次之后就会打印“done”。但事实并非如此:
        int count = 0;

        Semaphore s = new Semaphore(2, 2);
        for (int x = 0; x < 10; x++)
        {
            Thread t = new Thread(new ThreadStart(delegate()
            {
                s.WaitOne();
                Thread.Sleep(1000);
                Interlocked.Increment(ref count);                       
                s.Release();
            }));
            t.Start(x);
        }

        WaitHandle.WaitAll(new WaitHandle[] { s });
        Console.WriteLine("done: {0}", count);

输出:

done: 6

如果实现我所需的功能的唯一方法是将一个EventWaitHandle传递给每个线程,然后在这些EventWaitHandles数组上执行WaitAll(),那么仅对信号量数组执行WaitAll()有何意义呢?换句话说,等待线程何时解除阻塞?

很有可能你正在遇到本地化的竞态条件;Console.WriteLine周围有它自己的锁,输出内容的顺序不能保证与调用顺序相同。你可能想要编写滴答值或使用一个处理这种情况更好的日志框架。输出并不表明两个线程没有同时运行两个任务。 - casperOne
2
仅供参考,您可能想考虑使用Interlocked.Increment而不是volatile: https://dev59.com/VHVC5IYBdhLWcg3w4Vb6 - Chris Sinclair
casperOne,我不确定那是否正确,因为即使我用System.Diagnostics.Debug.WriteLine("done");替换最后一行仍然会遇到相同的问题。 - John Smith
我不太确定这个示例的目的是什么 - 无论如何,您都要启动10个线程。如果您想同时运行两个线程,为什么不只打开两个线程并均匀地分配工作呢? - caesay
如果你想等待所有线程完成,保留Thread对象并依次在每个对象上调用Join方法。 - Jon
2个回答

6

WaitHandle.WaitAll只是等待所有处理程序处于已发信号状态。

因此,当您在一个WaitHandle上调用WaitHandle.WaitAll时,它的工作方式与调用s.WaitOne()相同。

例如,您可以使用以下代码等待所有已启动的线程,但允许两个线程并行运行:

int count = 0;

Semaphore s = new Semaphore(2, 2);
AutoResetEvent[] waitHandles = new AutoResetEvent[10];
for (int x = 0; x < 10; x++)
    waitHandles[x] = new AutoResetEvent(false);

for (int x = 0; x < 10; x++)
{
    Thread t = new Thread(threadNumber =>
        {
            s.WaitOne();
            Thread.Sleep(1000);
            Interlocked.Increment(ref count);
            waitHandles[(int)threadNumber].Set();
            s.Release();
        });
    t.Start(x);
}

WaitHandle.WaitAll(waitHandles);
Console.WriteLine("done: {0}", count);

1
WaitHandle.WaitAll(new WaitHandle[] { s }); 等待的方式与 s.WaitOne(); 相同。它会在第一次机会时进入。您似乎期望此调用等待所有其他信号量操作,但操作系统无法区分。这个命令很可能是被授予访问信号量的第一个命令。
我认为您需要使用Barrier类。它适用于fork-join-style并行处理。

@caesay,Barrier类在网上肯定有很多教程可供参考。它使用起来很容易。 - usr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接