2010-02-25 7 views
11

나는 이것에 대한 답을 알아야만한다고 생각하지만, 어쨌든 내가 잠재적으로 치명적인 실수를 저지르고있는 경우에 대비하여 어쨌든 질문 할 것입니다.신호를 보내고 즉시 ManualResetEvent를 닫을 수 있습니까?

다음 코드는 오류없이/예외가 예상대로 실행 : 물론

static void Main(string[] args) 
{ 
    ManualResetEvent flag = new ManualResetEvent(false); 
    ThreadPool.QueueUserWorkItem(s => 
    { 
     flag.WaitOne(); 
     Console.WriteLine("Work Item 1 Executed"); 
    }); 
    ThreadPool.QueueUserWorkItem(s => 
    { 
     flag.WaitOne(); 
     Console.WriteLine("Work Item 2 Executed"); 
    }); 
    Thread.Sleep(1000); 
    flag.Set(); 
    flag.Close(); 
    Console.WriteLine("Finished"); 
} 

, 일반적으로 멀티 스레드 코드의 경우와 같이, 성공적인 시험이 실제로 스레드에 안전 것을 증명하지 않습니다. Close 뒤에 아무 것도 시도하지 않으면 문서에 명확하게 정의되지 않은 동작이 발생한다고 명시되어 있지만 을 Set 앞에 넣어도 테스트가 성공합니다. 나는 ManualResetEvent.Set 메소드를 호출 할 때

내 질문은, 그것이 호출자에게 제어를 반환 전에 모든 대기 스레드 신호 보장이다? 다시 말해서 WaitOne에 더 이상 전화가 걸리지 않는다고 보장 할 수 있다고 가정 할 때 여기서 핸들을 닫는 것이 안전 할 수도 있고 어떤 경우에는이 코드가 일부 웨이터의 신호 또는 결과를 방해 할 수도 있습니다 ObjectDisposedException에? 이 웨이터가 실제로 신호 얻을 것이다 경우에 대한 주장을하지 않는 것, 그래서 나는 확인하고 싶습니다 -

문서는 Set는 "이 상태를 신호"에두고 있다고 말한다.

+0

흥미로운 실험은 WaitOne() 직전에 Sleep을 이동하는 것입니다. 이렇게하면 'flag'가 Close() '일 때 WaitOne()이 수행하는 작업을 테스트 할 수 있습니다. –

+0

@ Philh Ngan : 문서에서 알 수 있듯이 실제로는 정의되지 않았습니다. 'Thread.Sleep' 전에 코드에서'Close'를 움직이면'WaitOne' 중에'ObjectDisposedException' ("안전 핸들이 닫혔습니다")을 얻습니다. 반면,'Thread.Sleep' 이후에 직접 Close *를 움직이면, 두 스레드 모두 신호를 받고 성공적으로 실행이 완료됩니다. 따라서 정의되지 않은 동작에는 "불량"코드에 경쟁 조건이 있습니다. 난 그냥 내 "좋은"코드라고 생각하는 것과 비슷한 정의되지 않은 동작을하지 않기를 원한다. :) – Aaronaught

답변

6

ManualResetEvent.Set으로 신호를 보내면 해당 이벤트를 기다리고있는 모든 스레드 (즉, 차단 상태가 flag.WaitOne)가 발신자에게 제어를 반환하기 전에 신호를받습니다.

은 물론 당신이 플래그를 설정할 수 있습니다 상황이 있습니다 그것은 어떤 일을하고 있기 때문에이 플래그를 확인하기 전에 스레드가 표시되지 않습니다 (또는 다중 스레드를 만드는 경우 nobugs는 제안) :

ThreadPool.QueueUserWorkItem(s => 
{ 
    QueryDB(); 
    flag.WaitOne(); 
    Console.WriteLine("Work Item 1 Executed"); 
}); 

플래그에 경합이 발생 했으므로 플래그를 닫을 때 정의되지 않은 동작을 만들 수 있습니다. 플래그는 스레드 간의 공유 리소스이므로 모든 스레드가 완료되면 신호를 보내는 카운트 다운 래치를 만들어야합니다. 이렇게하면 flag의 경합이 제거됩니다.

public class CountdownLatch 
{ 
    private int m_remain; 
    private EventWaitHandle m_event; 

    public CountdownLatch(int count) 
    { 
     Reset(count); 
    } 

    public void Reset(int count) 
    { 
     if (count < 0) 
      throw new ArgumentOutOfRangeException(); 
     m_remain = count; 
     m_event = new ManualResetEvent(false); 
     if (m_remain == 0) 
     { 
      m_event.Set(); 
     } 
    } 

    public void Signal() 
    { 
     // The last thread to signal also sets the event. 
     if (Interlocked.Decrement(ref m_remain) == 0) 
      m_event.Set(); 
    } 

    public void Wait() 
    { 
     m_event.WaitOne(); 
    } 
} 
  1. 카운트 래치 각 스레드 신호.
  2. 메인 스레드가 카운트 다운 래치에서 대기합니다.
  3. 카운트 다운 래치 신호 후 메인 스레드가 정리됩니다.

결국 궁극적으로 문제를 처리하는 안전한 방법이 아니며 대신 멀티 스레드 환경에서 100 % 안전하도록 프로그램을 설계해야합니다.

UPDATE : 단일 생산자/다중 소비자
여기에 가정이 프로듀서는 모든 소비자를 만든 후 , 당신은 소비자의 주어진 숫자로 CountdownLatch를 다시 생성 될 것입니다 얼마나 많은 소비자가 알고 있다는 것입니다 :

// In the Producer 
ManualResetEvent flag = new ManualResetEvent(false); 
CountdownLatch countdown = new CountdownLatch(0); 
int numConsumers = 0; 
while(hasMoreWork) 
{ 
    Consumer consumer = new Consumer(coutndown, flag); 
    // Create a new thread with each consumer 
    numConsumers++; 
} 
countdown.Reset(numConsumers); 
flag.Set(); 
countdown.Wait();// your producer waits for all of the consumers to finish 
flag.Close();// cleanup 
+0

개념 상 더 나은 옵션 일 수 있습니다. 여러 소비자가있는 단일 제작자의 상황에 어떻게 적용되는지 이해하는 데 어려움을 겪고 있습니다 (여러 제작자, 단일 소비자를 대상으로 한 것 같습니다). 이 래치가 그 시나리오에서 어떻게 사용될 것인지 더 설명 할 수 있습니까? – Aaronaught

+0

단일 제작자/다중 소비자 상황을 반영하여 답변을 업데이트했습니다. – Kiril

+0

OK, 알겠습니다. 이벤트 * 및 * 래치를 사용해야합니다. 불행히도이 경우 "생산자"는 실제로 얼마나 많은 소비자가 될지 전혀 알 수 없으며 현실은 테스트 코드보다 훨씬 복잡합니다 ...하지만 어떻게 든 적응할 수 있습니다. 잠시 후 가장 좋은 답변으로 밝혀지면이 것을 받아 들일 것입니다. – Aaronaught

5

괜찮지 않습니다. 두 스레드 만 시작했기 때문에 여기에서 운이 좋아지고 있습니다. 이중 코어 머신에서 Set을 호출하면 즉시 실행을 시작합니다. 대신이 시도하고 그것을 폭탄을 볼 : 그것은 다른 작업에 매우 바쁜 경우

static void Main(string[] args) { 
     ManualResetEvent flag = new ManualResetEvent(false); 
     for (int ix = 0; ix < 10; ++ix) { 
      ThreadPool.QueueUserWorkItem(s => { 
       flag.WaitOne(); 
       Console.WriteLine("Work Item Executed"); 
      }); 
     } 
     Thread.Sleep(1000); 
     flag.Set(); 
     flag.Close(); 
     Console.WriteLine("Finished"); 
     Console.ReadLine(); 
    } 

원래 코드가 오래된 기계 또는 현재 시스템에서 유사 실패합니다.

+4

이것은 완전히 정확하지만 테스트 코드 때문에 폭탄을 터뜨리는 것이지'Set'의 행동 때문이 아닙니다. 이 경우 일부 스레드가 실행되기 시작하기 전에 이벤트가 닫히고 있습니다. 절전 시간 초과를 5 초로 늘리면 쿼드 코어에서 정상적으로 작동합니다. 위의 시나리오는 이벤트에 대한 액세스를 직렬화하고 닫은 직후 null을 방지 할 수 있습니다. 나는 주로 대기 핸들에서 대기중인 스레드에 관심이 있습니다. – Aaronaught

+0

임의의 긴 절전 후에 실제로 스레드가 실행되기 시작한다는 보장은 없습니다. Sleeping long은 단지 ObjectDisposedException의 위험을 줄이며, 제거 할 수 없습니다. 이러한 종류의 타이밍 종속성은 스레딩에 대한 가정을 만드는 관의 궁극적 인 못입니다. –

+0

물론 프로덕션 코드에서'Thread.Sleep'을 사용하지 않을 것입니다 - 실제 구현은 다양한 동기화 프리미티브를 사용했습니다. 그러나 미묘한 경쟁 조건으로 밝혀졌으며, 이제는 수정되었습니다. 귀하의 의견을 보내 주셔서 감사합니다; 처음으로 이것을 부르는 +1. – Aaronaught

0

필자의 견해는 경쟁 조건이 있다는 것입니다. 조건 변수에 따라 작성 이벤트 객체를 갖는,이 같은 코드를 얻을 : 이벤트가 신호를받을 수있는 반면

mutex.lock(); 
while (!signalled) 
    condition_variable.wait(mutex); 
mutex.unlock(); 

그래서 이벤트를 기다리고 그래도 코드가 이벤트의 부품에 액세스해야 할 수 있습니다.

Close의 설명서에 따르면이 옵션은 관리되지 않는 리소스 만 해제합니다. 따라서 이벤트가 관리 리소스 만 사용하는 경우 운이 좋을 수 있습니다. 하지만 그것은 미래에 변할 수 있으므로 사전 예방 차원에서 오류를 범하고 더 이상 사용되지 않을 때까지 이벤트를 닫지 마십시오.

0

[current] 구현으로 인해 위험한 패턴처럼 보입니다. 여전히 사용중인 자원을 처리하려고합니다.

새 개체를 만드는 것과 같으며 개체를 사용하는 소비자가 작업하기 전에 맹목적으로 개체를 삭제하는 것과 같습니다.

그렇지 않은 경우에도 여기에 problem이 있습니다. 다른 스레드가 실행될 수 있기 전에도 프로그램이 종료 될 수 있습니다. 스레드 풀 스레드는 백그라운드 스레드입니다.

어쨌든 다른 스레드를 기다려야한다는 것을 감안할 때 나중에 정리할 수도 있습니다.

관련 문제