我正在使用信号量同步两个不同的进程:
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++)
我没有得到这种行为。
答案 0 :(得分:2)
我还没有找到任何文档说明等待信号量的线程必须按照它们开始等待的顺序发布。因此,进程B中的sem.WaitOne()
是否可能由sem.Release()
正好在其上方释放,而不给进程A提供进入的机会?
在等待进程B中的信号量之前添加Thread.Sleep()
可能会给进程A提供进入信号量的机会。
您还没有完全清楚为什么以这种方式使用信号量,或者进程B中的sem.WaitOne()
正在等待什么(因为进程A从未在您的信号量中释放信号量)示例代码),因此很难提出解决问题的改进措施。
如果您打算让进程A在开始工作之前等待进程B启动,那么对于进程B等待进程A在继续之前完成工作,似乎有两个{{1} } s可以更有效地实现这一目标(特别是&#34;流程B准备就绪&#34;流程A等待的事件,&#34;流程A完成&#34;流程B等待的事件)。
可能的解决方案:
流程A:
ManualResetEvent
流程B:
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();
}
答案 1 :(得分:1)
目前,进程B在释放之前不会使用信号量。根据文档,OpenExisting
不会消耗计数。如果有可用空间,WaitOne
将增加信号量计数。
因此,假设您首先运行A,因为它包含new Semaphore
而B是第二个,因为它包含OpenExisting
,A将抓住它并睡眠4秒并且从不释放它,B将尝试释放它在抓住之前。
至于WaitOne
没有等待,在我看来,与其他过程是竞争条件。 10ms允许其他进程首先进入,因此第二个进程中的WaitOne
似乎等待,因为没有空格。
信号量仅在没有空格时阻止,如果有空格WaitOne
将很快返回。
答案 2 :(得分:0)
作为您的具体问题的一般答案,正如其他人所指出的那样,您的代码中存在竞争条件。
这是大部分时间实际发生的事情,以及为什么你认为它是系统性的(实际上是随机的)。
你开始你的主题:
现在情景出现了分歧:
如果你有Thread.Sleep(100)但没有Thread.Sleep(10),.NET的内部实现将暂停你的线程并启动进程A,因为它正在等待信号量的那个最长的。
如果你有Thread.Sleep(10)可能(我不确定)该过程只旋转了10毫秒并继续进行上下文更改(也就是说,实际上没有让线程休眠),它继续重新获取信号量,因此进程A永远不会被解锁。当然,如果您没有任何类型的thread.sleep,也会发生同样的情况。基本上,使用Thread.Sleep(100),您强制进行上下文切换,从而允许进程A获取线程。这当然是完全随机的,因为无论如何都可以进行上下文切换,但是这样你强迫它,这就是你看到你提到的结果的原因。
编辑:对于您更新的问题,您认为错了。您使用信号量(在这种情况下为互斥)来同步两个进程或线程。所以你让其中一个线程获得信号量,另一个线程释放信号量,这样第二个线程就“发信号通知”它可以继续的第一个线程。
如果您希望该过程以两种方式工作,则必须使用两个信号量。