2

最后是编辑:

我正在使用信号量同步两个不同的进程:

var semaphore = new Semaphore(0, 1, "semName");

所以我有进程 A 和进程 B 它们碰巧是不同的可执行文件。

过程A

    static void Main(string[] args)
    {

        Console.Write("Process A");
        Task.Factory.StartNew(() =>
        {
            var semaphoreName = "sem";
            var semaphore = Semaphore.OpenExisting(semaphoreName);

            Thread.Sleep(100);

            semaphore.Release();

            semaphore.WaitOne();

            semaphore.Release();

            Console.Write("Process A Completed!");
        });
        Console.Read();

    }

进程 B(不同的控制台应用程序)

    static void Main(string[] args)
    {
        Console.Write("Process B");
        Task.Factory.StartNew(() =>
        {
            var semaphoreName = "sem";
            var semaphore = new Semaphore(0, 1, semaphoreName);

            semaphore.WaitOne();

            Thread.Sleep(1000);

            semaphore.Release();

            semaphore.WaitOne();

            Console.Write("Process B Completed!");
        });

        Console.Read();


    }

如果我调试进程或放置一个Thread.Sleep我能够到达最后一行Console.Write("Process A Completed!"); 我怎样才能解决这个问题而不必放置Thread.Sleep(100);




编辑

没有比赛条件!!也许我错了,如果我是的话,请纠正我。无论如何,这就是我认为没有竞争条件的原因:

过程A

    static void Main(string[] args)
    {
        Console.Write("Process A");
        var semaphoreName = "sem";

        Task.Factory.StartNew(() =>
        {                                
            var semaphore = Semaphore.OpenExisting(semaphoreName);

            semaphore.Release();

            semaphore.WaitOne();

            // this line should never be reached but it is!!!

            Console.Write("Process A Completed!");
        });            

        Console.Read();

    }

流程 B

    static void Main(string[] args)
    {
        Console.Write("Process B");
        var semaphoreName = "sem";

        Task.Factory.StartNew(() =>
        {                

            var semaphore = new Semaphore(0, 1, semaphoreName);

            semaphore.WaitOne();

            // important to have these lines
            int a = 0;
            for (var i = 0; i < 1000000000; i++)
                a = i;

            Thread.Sleep(10000); // there should not be a race condition any more!!!!

            Console.Write("Process B Completed!");
        });

        Console.Read();
    }

请注意,在流程 A 上,我们永远不应该到达这条线:Console.Write("Process A Completed!"); 但我们确实......

进程 B 休眠 10000 秒,因此没有理由应该存在竞争条件。此外,如果我删除循环for (var i = 0; i < 1000000000; i++),我不会得到那种行为。

4

3 回答 3

2

我找不到任何说明等待信号量的线程必须按照它们开始等待的顺序释放的文档。因此,在没有给进程 A 进入的机会的情况下,sem.WaitOne()进程中的 B 是否可能被其上层释放?sem.Release()

在进程 B 中在信号量上等待之前的添加Thread.Sleep()可能会给进程 A 一个进入信号量的机会。

您还没有完全清楚为什么要以这种方式使用信号量,或者sem.WaitOne()进程 B 正在等待什么(因为进程 A 从未在示例代码中释放信号量),因此很难提出改进建议解决问题。

但是,如果您打算让进程 A 在开始工作之前等待进程 B 启动,然后让进程 B 在继续之前等待进程 A 完成工作,似乎两个ManualResetEvents 可以更有效地实现这一点(特别是进程 A 等待“进程 B 就绪”事件,进程 B 等待“进程 A 完成”事件)。

可能的解决方案:

过程一:

    static void Main(string[] args)
    {
        Console.Write("Process A");
        Task.Factory.StartNew(() =>
        {
            var readyEvent = new EventWaitHandle(false, EventResetMode.ManualReset, "Process B Ready");
            var doneEvent = new EventWaitHandle(false, EventResetMode.ManualReset, "Process A Finished");

            // Wait for process B to be ready...
            readyEvent.WaitOne();

            // Do some work...

            Console.Write("Process A Completed!");

            // Signal that the process is complete
            doneEvent.Set();
        });
        Console.Read();
    }

过程乙:

    static void Main(string[] args)
    {
        Console.Write("Process B");
        Task.Factory.StartNew(() =>
        {
            var readyEvent = new EventWaitHandle(false, EventResetMode.ManualReset, "Process B Ready");
            var doneEvent = new EventWaitHandle(false, EventResetMode.ManualReset, "Process A Finished");

            // Signal that process B is ready
            readyEvent.Set();

            // Wait for process A to complete...
            doneEvent.WaitOne();

            Console.Write("Process B Completed!");
        });

        Console.Read();
    }
于 2012-08-10T15:46:01.203 回答
1

目前,进程 B 在释放信号量之前不消耗信号量。OpenExisting根据文档不消耗计数。WaitOne如果有可用空间,将增加信号量计数。

因此,假设您首先运行 A 因为它包含new Semaphore而 B 第二因为它包含OpenExisting,A 将抓取它并休眠 4 秒并且永远不会释放它,并且 B 会在抓取它之前尝试释放它。

至于WaitOne不等待,在我看来这是与其他进程的竞争条件。10ms 允许其他进程首先进入,因此WaitOne在第二个进程中似乎等待,因为没有空间。

信号量只有在没有空间的时候才会阻塞,如果有空间WaitOne会很快返回。

于 2012-08-10T15:50:05.767 回答
0

正如其他人指出的那样,作为对您的特定问题的一般回答,您的代码中存在竞争条件。

这就是大多数时候实际发生的事情,也是为什么你认为它是系统的(实际上是随机的)。

你开始你的线程:

  • A 启动并在信号量中被阻塞。
  • B 启动并释放信号量上的锁。

现在情况不同了:

如果您有 Th​​read.Sleep(100) 但没有 Thread.Sleep(10),.NET 的内部实现将暂停您的线程并启动进程 A,因为它是等待信号量最长的进程。

如果你有 Thread.Sleep(10) 可能(我不确定)进程只是旋转 10 毫秒并继续而不做上下文更改(也就是说,没有真正让线程进入睡眠状态),它会继续重新获取信号量,因此进程 A 永远不会被解锁。如果您没有任何类型的 thread.sleep,当然也会发生同样的情况。基本上,使用 Thread.Sleep(100) 您正在强制进行上下文切换,从而允许进程 A 获取线程。这当然是完全随机的,因为上下文切换无论如何都可能出现,但这样你就会强制它,这就是你看到你提到的结果的原因。

编辑:对于您更新的问题,您认为它是错误的。您使用信号量(在这种情况下为互斥体)来同步两个进程或线程。所以你让一个线程获取信号量,而另一个线程释放信号量,这样第二个线程就“发出信号”,它可以继续第一个线程。

如果您希望该过程以两种方式工作,则必须使用两个信号量。

于 2012-08-10T15:59:29.893 回答