2012-09-03 2 views
1

작은 멀티 스레드 네트워크 서버를 쓰고 있습니다. 모든 고전적인 기능 : 들어오는 연결을 수신하고이를 받아들이고 다른 스레드에서 제공합니다. 또한,이 서버는 때때로 다시 시작해야하며, 그렇게하기 위해서는 a) 듣기를 멈추고, b) 연결된 모든 클라이언트를 쫓아 내고, c) 일부 설정을 조정/대기하고, d) 듣기를 다시 시작해야합니다.컬렉션 동기화 및 작업 중단

글쎄, 나는 꽤 많이 멀티 스레드 프로그램을 개발하는 것에 대해 모른다. 그래서 나는 도움을 요청하고있다. 다음은 내가 온 것입니다 (핵심 자료 만 해당).

class Server 
{ 
    class MyClient 
    { 
     Server server; 
     TcpClient client; 
     bool hasToFinish = false; 

     public MyClient(Server server, TcpClient client) 
     { 
      this.server = server; 
      this.client = client; 
     } 

     public void Go() 
     { 
      while (!hasToFinish) 
      { 
       // do all cool stuff 
      } 
      CleanUp(); 
     } 

     private void CleanUp() 
     { 
      // finish all stuff 

      client.Close(); 
      server.myClients.Remove(this); 
     } 

     public void Finish() 
     { 
      hasToFinish = true; 
     } 
    } 

    bool running = false; 
    TcpListener listener; 
    HashSet<MyClient> myClients = new HashSet<MyClient>(); 

    public void Start() 
    { 
     if (running) 
      return; 

     myClients.Clear(); 
     listener = new TcpListener(IPAddress.Parse("127.0.0.1"), 1234); 
     listener.Start(); 
     listener.BeginAcceptTcpClient(AcceptClient, this); 
     running = true; 
    } 

    public void Stop() 
    { 
     if (!running) 
      return; 

     listener.Stop(); 
     foreach (MyClient client in myClients) 
     { 
      client.Finish(); 
     } 
     myClients.Clear(); 
     running = false; 
    } 

    public void AcceptClient(IAsyncResult ar) 
    { 
     MyClient client = new MyClient(this, ((TcpListener)ar.AsyncState).EndAcceptTcpClient(ar)); 
     myClients.Add(client); 
     client.Go(); 
    } 
} 

절대적으로 만족스럽지 않습니다. sychronizing이 없다 (나는 어디에 넣을 지 모른다!), Server.Stop()을 호출한다고해서 MyClient가 즉시 멈추지는 않는다. 이 문제를 어떻게 수정합니까?

답변

1

코드가 매우 깨끗해 보이며 간단한 수정으로 스레드로부터 안전하게 만들 수 있습니다.

"클라이언트", "서버"및 클라이언트 - 서버 상호 작용의 세 부분으로 나뉩니다.

먼저 클라이언트는 Go() 메서드를 한 스레드에서 호출하고 (A라고 함) Finish() 메서드를 다른 스레드에서 호출합니다 (B). 스레드 B가 hasToFinish 필드를 수정하면 변수가 CPU 캐시에 캐시 될 수 있기 때문에 스레드 A가 수정 내용을 즉시 보지 못할 수 있습니다. hasToFinish 필드를 "volatile"로 지정하면 스레드 B가 스레드 A에 변수 변경을 게시하도록 강제 할 수 있습니다.

이제 서버 클래스. 아래 예와 같이 "Server"인스턴스에서 세 가지 메소드를 동기화하는 것이 좋습니다. Start와 Stop이 순차적으로 호출되고 변경된 변수가 스레드를 통해 게시되는지 확인합니다.

클라이언트 - 서버 상호 작용도 처리해야합니다. 코드에서 클라이언트는 서버에서 해당 참조를 제거하지만 서버는 Finish()를 수행 할 때 모든 클라이언트 참조를 지 웁니다. 나에게 불필요한 것처럼 보입니다. 클라이언트에서 코드의 일부를 제거 할 수 있다면 걱정할 필요가 없습니다. 어떤 이유로 서버가 아닌 클라이언트에 논리를 유지하도록 선택하면 Server 클래스에서 public 메서드 호출 RemoveClient (클라이언트 클라이언트)를 만들고이를 서버 인스턴스와 동기화합니다. 그런 다음 클라이언트가 직접 HashSet을 조작하는 대신이 메소드를 호출하게하십시오.

이 문제가 해결되기를 바랍니다.

public void Start() 
{ 
    lock(this) 
    { 
    if (running) 
     return; 

    myClients.Clear(); 
    listener = new TcpListener(IPAddress.Parse("127.0.0.1"), 1234); 
    listener.Start(); 
    listener.BeginAcceptTcpClient(AcceptClient, this); 
    running = true; 
    } 
} 

public void Stop() 
{ 
    lock(this) 
    { 
    if (!running) 
     return; 

    listener.Stop(); 
    foreach (MyClient client in myClients) 
    { 
     client.Finish(); 
    } 
    myClients.Clear(); 
    running = false; 
    } 
} 

public void AcceptClient(IAsyncResult ar) 
{ 
    lock(this) 
    { 
    MyClient client = new MyClient(this, ((TcpListener)ar.AsyncState).EndAcceptTcpClient(ar)); 
    myClients.Add(client); 
    client.Go(); 
    } 
} 
+0

btw, 휘발성을 사용할시기와 잠금을 사용할시기를 알아 보려면 "Java 동시성"을 권장합니다. 언제, 왜 사용해야하는지 설명합니다. 이 책은 Java에 관한 내용이지만 C sharp에도 적용됩니다. – nwang0