2017-03-17 7 views
0

명명 된 파이프에 문제가 있습니다. 30 개의 클라이언트 파이프가 모두 동시에 연결하려고 할 때, 4 코어 머신의 로컬 파이프 서버에 시간 초과 또는 세마포어 시간 초과가 발생합니다. 때로는 가장 긴 시간 동안 한 클라이언트가 연결을 설정하는 데 1 초가 걸립니다. 그런 다음에 다음 번에 한 번 더. 로컬 파이프 액세스가 빠르다고 생각했습니다. 왜 30 대의 클라이언트, 심지어 100 대의 클라이언트가 같은 시간을 소비합니까? 하나의 연결을 만들기 위해 1000 밀리 초가 걸리는 이유는 무엇입니까?명명 된 파이프가 로컬 파이프 서버에 연결하는 데 예기치 않게 오래 걸리는 이유는 무엇입니까?

using System; 
using System.Diagnostics; 
using System.IO.Pipes; 
using System.Security.AccessControl; 
using System.Threading; 
using System.Threading.Tasks; 

namespace PipeStress 
{ 
    class Program 
    { 
     public static PipeSecurity CreatePipeSecurity() 
     { 
      PipeSecurity ps; 
      using (var seedPipe = new NamedPipeServerStream("{DDAB520F-5104-48D1-AA65-7AEF568E0045}", 
       PipeDirection.InOut, 1, PipeTransmissionMode.Message, PipeOptions.None, 1000, 1000)) 
      { 
       ps = seedPipe.GetAccessControl(); 
      } 

      var sid = new System.Security.Principal.SecurityIdentifier(
       System.Security.Principal.WellKnownSidType.BuiltinUsersSid, null); 

      ps.AddAccessRule(new PipeAccessRule(sid, PipeAccessRights.ReadWrite | 
       PipeAccessRights.CreateNewInstance | PipeAccessRights.ChangePermissions, 
       AccessControlType.Allow)); 

      sid = new System.Security.Principal.SecurityIdentifier(
       System.Security.Principal.WellKnownSidType.LocalServiceSid, null); 

      ps.AddAccessRule(new PipeAccessRule(sid, PipeAccessRights.ReadWrite, 
       AccessControlType.Allow)); 

      return ps; 
     } 
     static void Main(string[] args) 
     { 
      Task.Run(() => RunPipeServer()); 

      for (var i = (uint) 0; i < 30; i++) 
      { 
       var index = i; 
       //Thread.Sleep(100); 
       Task.Run(() => RunPipeClient(index)); 
      } 

      Console.ReadLine(); 
     } 

     private const string PipeName = "{6FDABBF8-BFFD-4624-A67B-3211ED7EF0DC}"; 

     static void RunPipeServer() 
     { 
      try 
      { 
       var stw = new Stopwatch(); 

       while (true) 
       { 
        stw.Restart(); 

        var pipeServer = new NamedPipeServerStream(PipeName, PipeDirection.InOut, 
         NamedPipeServerStream.MaxAllowedServerInstances, 
         PipeTransmissionMode.Message, PipeOptions.Asynchronous, 4 * 1024, 4 * 1024, 
         CreatePipeSecurity()); 
        try 
        { 
         var pipe = pipeServer; 
         pipeServer.WaitForConnection(); 
         Console.WriteLine(stw.ElapsedMilliseconds + "ms"); 


         Task.Run(() => HandleClient(pipe)); 
        } 
        catch (Exception ex) 
        { 
         pipeServer.Dispose(); 
        } 
       } 
      } 
      catch (Exception ex) 
      { 
       Console.WriteLine(ex); 
      } 
     } 

     private static void HandleClient(NamedPipeServerStream pipeServer) 
     { 
      try 
      { 
       try 
       { 
        //Thread.Sleep(100); 
       } 
       finally 
       { 
        pipeServer.Close(); 
       } 
      } 
      finally 
      { 
       pipeServer.Dispose(); 
      } 
     } 

     static void RunPipeClient(uint i) 
     { 
      try 
      { 
       var j = 0; 

       while (true) 
       { 

        using (var pipeClient = new NamedPipeClientStream(".", PipeName, PipeDirection.InOut, PipeOptions.None)) 
        { 
         //Thread.Sleep(100); 

         pipeClient.Connect(5000); 
         try 
         { 
          Console.WriteLine($"{i}, {++j}"); 
          pipeClient.ReadByte(); 
         } 
         finally 
         { 
          pipeClient.Close(); 
         } 
        } 


       } 
      } 
      catch (Exception ex) 
      { 
       Console.WriteLine(ex); 
      } 
     } 
    } 
} 

답변

3

서버에 부하를 추가 할 때 약간의 대기 시간이 예상됩니다. 그러나 귀하의 예에서 대기 시간은 정확히 1 초 간격으로 발생하며 과도하고 눈에 띄게 질서 정연합니다. 질서는 무슨 일이 벌어지고 있는지에 대한 매우 큰 단서입니다. :)

실제로보고있는 대기 시간은 새 스레드 생성을위한 스레드 풀에 내장 된 지연 때문입니다. 증거의 또 다른 부분은 사실, 처음 몇 번의 작업이 즉시 완료된다는 사실입니다. 대기 시간은 스레드 풀이 끝난 스레드 풀과 정확히 일치하는 이후에만 발생하며 요청을 처리하기 위해 새 스레드를 만들 수 있도록 스레드 풀의 제한을 기다리고 있습니다. 이 스로틀 링은 새로운 스레드 생성을 놀라 울 정도로 제한합니다! :), 초당 하나.

이 문제를 해결하는 방법 중 하나는 스레드 풀의 최소 스레드 수를 늘려 필요한 스레드를 모두 즉시 확보하는 것입니다. 이는 ThreadPool.SetMinThreads()으로 전화하여 수행 할 수 있습니다. 그러나 실제로는 각 클라이언트 (및 서버)에 스레드 풀 스레드를 전용하는 것은 낭비입니다. API를 비동기 적으로 사용하고 .NET에서 I/O를 관리하도록하는 것이 좋습니다. 쓰레드 풀 쓰레드는 여전히 사용되지만, 실제로 필요할 때, 즉 I/O 작업이 실제로 완료 될 때만, 그리고 그 완료를 처리하기 위해서만 사용됩니다. 처음에는 더 적은 수의 스레드가 필요하며 스레드에 대한 요구가 증가함에 따라 스레드 풀이 더 빨리 평형에 도달합니다.

static void Main(string[] args) 
    { 
     CancellationTokenSource tokenSource = new CancellationTokenSource(); 
     List<Task> tasks = new List<Task>(); 

     tasks.Add(RunPipeServer(tokenSource.Token)); 

     for (var i = (uint)0; i < 30; i++) 
     { 
      var index = i; 
      tasks.Add(RunPipeClient(index, tokenSource.Token)); 
     } 

     Console.ReadLine(); 
     tokenSource.Cancel(); 

     Task.WaitAll(tasks.ToArray()); 
    } 

    private const string PipeName = "{6FDABBF8-BFFD-4624-A67B-3211ED7EF0DC}"; 

    static async Task RunPipeServer(CancellationToken token) 
    { 
     try 
     { 
      var stw = new Stopwatch(); 
      int clientCount = 0; 

      while (!token.IsCancellationRequested) 
      { 
       stw.Restart(); 

       var pipeServer = new NamedPipeServerStream(PipeName, PipeDirection.InOut, 
        NamedPipeServerStream.MaxAllowedServerInstances, 
        PipeTransmissionMode.Message, PipeOptions.Asynchronous); 
       try 
       { 
        token.Register(() => pipeServer.Close()); 
        await Task.Factory.FromAsync(pipeServer.BeginWaitForConnection, pipeServer.EndWaitForConnection, null); 
        clientCount++; 
        Console.WriteLine($"server connection #{clientCount}. {stw.ElapsedMilliseconds} ms"); 

        HandleClient(pipeServer); 
       } 
       catch (Exception ex) 
       { 
        Console.WriteLine("RunPipeServer exception: " + ex.Message); 
        pipeServer.Dispose(); 
       } 
      } 
     } 
     catch (Exception ex) 
     { 
      Console.WriteLine("RunPipeServer exception: " + ex.Message); 
      Console.WriteLine(ex); 
     } 
    } 

    // Left this method synchronous, as in your example it does almost nothing 
    // in this example. You might want to make this "async Task..." as well, if 
    // you wind up having this method do anything interesting. 
    private static void HandleClient(NamedPipeServerStream pipeServer) 
    { 
     pipeServer.Close(); 
    } 

    static async Task RunPipeClient(uint i, CancellationToken token) 
    { 
     try 
     { 
      var j = 0; 

      while (!token.IsCancellationRequested) 
      { 
       using (var pipeClient = new NamedPipeClientStream(".", PipeName, PipeDirection.InOut, PipeOptions.None)) 
       { 
        pipeClient.Connect(5000); 
        try 
        { 
         Console.WriteLine($"connected client {i}, connection #{++j}"); 
         await pipeClient.ReadAsync(new byte[1], 0, 1); 
        } 
        finally 
        { 
         pipeClient.Close(); 
        } 
       } 
      } 

      Console.WriteLine($"client {i} exiting normally"); 
     } 
     catch (Exception ex) 
     { 
      Console.WriteLine($"RunPipeClient({i}) exception: {ex.Message}"); 
     } 
    } 
: 여기

이 코드의 버전이이 작업을 수행 할 수있는 방법을 설명입니다 (당신이에 대해 물어하고있는 문제와 관련된 어떤 식 으로든 것으로 나타나지 않는 한 나는, 전부 CreatePipeSecurity() 방법을 제거)
+0

응답 해 주셔서 감사합니다. 그것은 매우 도움이되고 계몽되었습니다. – PieterB