C# 多线程服务器中的Monitor.Wait/Pulse争用条件
我在多线程TCP服务器中的联锁Monitor.Wait和Monitor.Pulse出现问题。为了演示我的问题,以下是我的服务器代码:C# 多线程服务器中的Monitor.Wait/Pulse争用条件,c#,multithreading,client-server,C#,Multithreading,Client Server,我在多线程TCP服务器中的联锁Monitor.Wait和Monitor.Pulse出现问题。为了演示我的问题,以下是我的服务器代码: public class Server { TcpListener listener; Object sync; IHandler handler; bool running; public Server(IHandler handler, int port) { this.handler = ha
public class Server
{
TcpListener listener;
Object sync;
IHandler handler;
bool running;
public Server(IHandler handler, int port)
{
this.handler = handler;
IPAddress address = Dns.GetHostEntry(Dns.GetHostName()).AddressList[0];
listener = new TcpListener(address, port);
sync = new Object();
running = false;
}
public void Start()
{
Thread thread = new Thread(ThreadStart);
thread.Start();
}
public void Stop()
{
lock (sync)
{
listener.Stop();
running = false;
Monitor.Pulse(sync);
}
}
void ThreadStart()
{
if (!running)
{
listener.Start();
running = true;
lock (sync)
{
while (running)
{
try
{
listener.BeginAcceptTcpClient(new AsyncCallback(Accept), listener);
Monitor.Wait(sync); // Release lock and wait for a pulse
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
}
}
void Accept(IAsyncResult result)
{
// Let the server continue listening
lock (sync)
{
Monitor.Pulse(sync);
}
if (running)
{
TcpListener listener = (TcpListener)result.AsyncState;
using (TcpClient client = listener.EndAcceptTcpClient(result))
{
handler.Handle(client.GetStream());
}
}
}
}
这是我的客户代码:
class Client
{
class EchoHandler : IHandler
{
public void Handle(Stream stream)
{
System.Console.Out.Write("Echo Handler: ");
StringBuilder sb = new StringBuilder();
byte[] buffer = new byte[1024];
int count = 0;
while ((count = stream.Read(buffer, 0, 1024)) > 0)
{
sb.Append(Encoding.ASCII.GetString(buffer, 0, count));
}
System.Console.Out.WriteLine(sb.ToString());
System.Console.Out.Flush();
}
}
static IPAddress localhost = Dns.GetHostEntry(Dns.GetHostName()).AddressList[0];
public static int Main()
{
Server server1 = new Server(new EchoHandler(), 1000);
Server server2 = new Server(new EchoHandler(), 1001);
server1.Start();
server2.Start();
Console.WriteLine("Press return to test...");
Console.ReadLine();
// Note interleaved ports
SendMsg("Test1", 1000);
SendMsg("Test2", 1001);
SendMsg("Test3", 1000);
SendMsg("Test4", 1001);
SendMsg("Test5", 1000);
SendMsg("Test6", 1001);
SendMsg("Test7", 1000);
Console.WriteLine("Press return to terminate...");
Console.ReadLine();
server1.Stop();
server2.Stop();
return 0;
}
public static void SendMsg(String msg, int port)
{
IPEndPoint endPoint = new IPEndPoint(localhost, port);
byte[] buffer = Encoding.ASCII.GetBytes(msg);
using (Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
s.Connect(endPoint);
s.Send(buffer);
}
}
}
客户端发送七条消息,但服务器仅打印四条:
Press return to test...
Press return to terminate...
Echo Handler: Test1
Echo Handler: Test3
Echo Handler: Test2
Echo Handler: Test4
调用服务器的Stop()
方法时会显示剩余的消息(即,唤醒服务器的sync
对象会导致它分派剩余的传入消息)。在我看来,这只能发生在ThreadStart
和Accept
方法之间的竞争条件下,但是sync
对象周围的锁应该会阻止这种情况
有什么想法吗
非常感谢,,
西蒙
请注意,我知道输出出现故障等,我特别询问锁和监视器之间的竞争条件。干杯,上海。问题是您正在使用脉冲/等待作为信号。一个适当的信号,如AutoResetEvent,其状态是在线程调用WaitOne()之前一直保持信号状态。在没有任何线程等待的情况下调用Pulse将成为一个noop 这与同一个线程可以多次使用锁这一事实相结合。因为您使用的是异步编程,所以可以由执行BeginAcceptTcpClient的同一线程调用Accept回调 让我举例说明。我注释掉了第二台服务器,并在您的服务器上更改了一些代码
void ThreadStart()
{
if (!running)
{
listener.Start();
running = true;
lock (sync)
{
while (running)
{
try
{
Console.WriteLine("BeginAccept [{0}]",
Thread.CurrentThread.ManagedThreadId);
listener.BeginAcceptTcpClient(new AsyncCallback(Accept), listener);
Console.WriteLine("Wait [{0}]",
Thread.CurrentThread.ManagedThreadId);
Monitor.Wait(sync); // Release lock and wait for a pulse
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
}
}
void Accept(IAsyncResult result)
{
// Let the server continue listening
lock (sync)
{
Console.WriteLine("Pulse [{0}]",
Thread.CurrentThread.ManagedThreadId);
Monitor.Pulse(sync);
}
if (running)
{
TcpListener localListener = (TcpListener)result.AsyncState;
using (TcpClient client = localListener.EndAcceptTcpClient(result))
{
handler.Handle(client.GetStream());
}
}
}
我的跑步输出如下所示。如果您自己运行这段代码,这些值会有所不同,但总体上是相同的
Press return to test...
BeginAccept [3]
Wait [3]
Press return to terminate...
Pulse [5]
BeginAccept [3]
Pulse [3]
Echo Handler: Test1
Echo Handler: Test3
Wait [3]
正如您所看到的,有两个脉冲被调用,一个来自单独的线程(脉冲[5]),它唤醒了第一次等待。然后,线程3执行另一个BeginAccept,但具有挂起的传入连接,该线程决定立即调用Accept回调。由于Accept由同一线程调用,所以锁(sync)不会阻塞,而是在空线程队列上立即脉冲[3]
调用两个处理程序并处理这两条消息
一切正常,ThreadStart再次开始运行并无限期等待
现在,这里的根本问题是您试图使用监视器作为信号。因为它不记得第二个脉冲的状态,所以它丢失了
但有一个简单的解决办法。使用AutoResetEvents,这是一个正确的信号,它将记住其状态
public Server(IHandler handler, int port)
{
this.handler = handler;
IPAddress address = Dns.GetHostEntry(Dns.GetHostName()).AddressList[0];
listener = new TcpListener(address, port);
running = false;
_event = new AutoResetEvent(false);
}
public void Start()
{
Thread thread = new Thread(ThreadStart);
thread.Start();
}
public void Stop()
{
listener.Stop();
running = false;
_event.Set();
}
void ThreadStart()
{
if (!running)
{
listener.Start();
running = true;
while (running)
{
try
{
listener.BeginAcceptTcpClient(new AsyncCallback(Accept), listener);
_event.WaitOne();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
}
void Accept(IAsyncResult result)
{
// Let the server continue listening
_event.Set();
if (running)
{
TcpListener localListener = (TcpListener) result.AsyncState;
using (TcpClient client = localListener.EndAcceptTcpClient(result))
{
handler.Handle(client.GetStream());
}
}
}
谢谢你。我假设BeginAcceptTcpClient总是在一个单独的线程上运行,因此我可以使用sync对象作为关键部分。你是正确的,信号是前进的方向。再次感谢。嘘
public Server(IHandler handler, int port)
{
this.handler = handler;
IPAddress address = Dns.GetHostEntry(Dns.GetHostName()).AddressList[0];
listener = new TcpListener(address, port);
running = false;
_event = new AutoResetEvent(false);
}
public void Start()
{
Thread thread = new Thread(ThreadStart);
thread.Start();
}
public void Stop()
{
listener.Stop();
running = false;
_event.Set();
}
void ThreadStart()
{
if (!running)
{
listener.Start();
running = true;
while (running)
{
try
{
listener.BeginAcceptTcpClient(new AsyncCallback(Accept), listener);
_event.WaitOne();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
}
void Accept(IAsyncResult result)
{
// Let the server continue listening
_event.Set();
if (running)
{
TcpListener localListener = (TcpListener) result.AsyncState;
using (TcpClient client = localListener.EndAcceptTcpClient(result))
{
handler.Handle(client.GetStream());
}
}
}