2009-05-12 14 views
8

में मुझे एक बहु-थ्रेडेड टीसीपी सर्वर में इंटरलॉक मॉनिटर.एट और मॉनीटर.पल्स के साथ समस्या हो रही है। मेरी मुद्दों प्रदर्शित करने के लिए, यहाँ अपने सर्वर कोड है:मॉनिटर.एट/पल्स रेस कंडीशन मल्टीथ्रेडेड सर्वर

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 

मुझे संदेह है कि को Wait से पहले होने पर मॉनीटर Pulse (सर्वर की Accept विधि में) को अनुमति देकर भ्रमित हो रहा है (i ThreadStart विधि), भले ही ThreadStart को sync ऑब्जेक्ट पर लॉक होना चाहिए, जब तक कि यह Monitor.Wait() पर कॉल न करे, और फिर Accept विधि लॉक प्राप्त कर सकती है और Pulse भेज सकती है। आप सर्वर के Stop() विधि में इन दो पंक्तियों बाहर टिप्पणी करते हैं:

//listener.Stop(); 
//running = false; 

शेष संदेश दिखाई देते हैं जब सर्वर के Stop() विधि कहा जाता है (यदि सर्वर के sync वस्तु जागने यह शेष भेजे गए संदेशों प्रेषण करने के लिए कारण बनता है)। ऐसा लगता है कि यह केवल ThreadStart और Accept विधियों के बीच दौड़ की स्थिति में हो सकता है, लेकिन sync ऑब्जेक्ट के आसपास लॉक को इसे रोकना चाहिए।

कोई विचार?

बहुत धन्यवाद, साइमन।

ps। ध्यान दें कि मुझे पता है कि उत्पादन आउट-ऑफ-ऑर्डर इत्यादि दिखाई देता है, मैं विशेष रूप से ताले और मॉनीटर के बीच दौड़ की स्थिति के बारे में पूछ रहा हूं। चीयर्स, एसएच।

उत्तर

5

समस्या यह है कि आप सिल्वर के रूप में पल्स/प्रतीक्षा का उपयोग कर रहे हैं। एक उचित सिग्नल, जैसे ऑटोरेटेवेंट में एक ऐसा राज्य होता है, जब तक कि थ्रेड को WaitOne() कहा जाता है, तब तक यह संकेतित रहता है। किसी भी धागे के बिना पल्स को कॉल करना बिना इंतजार कर देगा।

यह इस तथ्य के साथ संयुक्त है कि एक ही थ्रेड द्वारा कई बार लॉक लिया जा सकता है। चूंकि आप Async प्रोग्रामिंग का उपयोग कर रहे हैं, स्वीकृति कॉलबैक को उसी थ्रेड द्वारा बुलाया जा सकता है जिसने BeginAcceptTcpClient किया था।

मुझे बताएं। मैंने दूसरे सर्वर पर टिप्पणी की, और आपके सर्वर पर कुछ कोड बदल दिया।

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 करता है, लेकिन लंबित आने वाले कनेक्शन होने पर थ्रेड तुरंत कॉलबैक स्वीकार करने का निर्णय लेता है। चूंकि स्वीकृति को उसी थ्रेड द्वारा बुलाया जाता है, इसलिए लॉक (सिंक) ब्लॉक नहीं होता है लेकिन पल्स [3] तुरंत खाली थ्रेड कतार पर होता है।

दो हैंडलर बुलाए जाते हैं और दो संदेशों को संभालते हैं।

सबकुछ ठीक है, और थ्रेड स्टार्ट फिर से दौड़ना शुरू कर देता है और अनिश्चित काल तक प्रतीक्षा करता है।

अब, अंतर्निहित मुद्दा यह है कि आप एक मॉनिटर को सिग्नल के रूप में उपयोग करने का प्रयास कर रहे हैं। चूंकि यह राज्य को याद नहीं करता है, दूसरा पल्स खो गया है।

लेकिन इसके लिए एक आसान समाधान है। 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()); 
     } 
    } 
} 
+0

धन्यवाद मैट। मैंने माना कि BeginAcceptTcpClient हमेशा एक अलग धागे पर चला गया और इस प्रकार मैं सिंक ऑब्जेक्ट को एक महत्वपूर्ण खंड के रूप में उपयोग कर सकता था। आप जगह पर थे और सिग्नल जाने का रास्ता हैं। एक बार फिर धन्यवाद। एसएच –

संबंधित मुद्दे