ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 생산자/소비자 패턴에서의 WaitForSingleObject()
    개발/C·C++ 2021. 9. 1. 18:36

    윈도우에서 제공하는 이벤트 객체와 WaitForSingleObject()를 통해서 실행순서를 동기화할 수 있습니다. 이것이 중요한 이유는 소비자 스레드는 생산자 스레드에서 큐 자료구조에 데이터를 넣어줘야 그 데이터를 꺼내서 필요한 작업을 할 수 있는데, 대기 시간이 길어질 경우 소비자 스레드가 무한 대기하는 것은 효율적으로 CPU를 사용하는 것이 아니기 때문입니다. 대기하는 것에만 CPU 시간을 쓰는 것은 아깝습니다. 

    mutex m;
    queue<int32> q;
    HANDLE handle;
    
    void Producer()
    {
    	while (true)
    	{
    		{
    			unique_lock<mutex> lock(m);
    			q.push(100);
    		}
    		this_thread::sleep_for(100ms);
    	}
    }
    
    void Consumer()
    {
    	while (true)
    	{
    		unique_lock<mutex> lock(m);
    		if (false == q.empty())
    		{
    			int32 data = q.front();
    			q.pop();
    			// lock 걸고 출력하는 게 좋은 건 아니지만 학습을 위해			
    			cout << data << endl;
    		}
    	}
    }

    이 코드를 실행하면 CPU 점유율이 일정하게 유지됩니다. 100ms 동안 아무 것도 안 하고 대기하느라 말이죠.

    13%

     

    데이터가 없으면 소비자 스레드는 블록 상태로 들어가고 그동안 CPU는 다른 스레드에 제어권이 넘어가 있다가 데이터가 들어왔을 때 스레드를 깨워 할 일을 시키는 게 더 효율적입니다. 이를 위해 윈도우의 이벤트 객체와 WaitForSingleObject()를 사용할 수 있습니다. 이벤트 객체는 CreateEvent() 함수를 통해 만들 수 있습니다. 지금 상황에선 두 번째 인자에 false를 넣어 자동으로 상태가 변경되도록 하고, 초기값은 non-signaled로 하기 위해 false를 넣겠습니다.

     

    기본 흐름은 생산자 스레드에 데이터가 들어왔을 때 SetEvent(handle)를 호출해 handle을 signaled로 바꿔줍니다. WaitForSingleObject()에 전달된 핸들이 non-signaled 일 때 해당 스레드가 블락됩니다. 핸들이 signaled로 바뀌었을 때 함수가 반환되기 때문에 실행 순서를 동기화할 수 있습니다. 여기에서 이벤트를 자동으로 설정했을 경우에는 WaitForSingleObject()가 반환되면서 상태가 non-signaled로 변경됩니다. 다른 소비자 스레드 스레드가 있을 경우 WaitForSingleObject()에서 블락될 겁니다. 수동일 경우에는 필요에 따라 ResetEvent(handle)을 호출해서 non-signaled로 변경해줍니다.

    mutex m;
    queue<int32> q;
    HANDLE handle;
    
    void Producer()
    {
    	while (true)
    	{
    		{
    			unique_lock<mutex> lock(m);
    			q.push(100);
    		}
    		// to signaled
    		SetEvent(handle);
    		this_thread::sleep_for(100ms);
    	}
    }
    
    void Consumer()
    {
    	while (true)
    	{
    		WaitForSingleObject(handle, INFINITE);
    		unique_lock<mutex> lock(m);
    		if (false == q.empty())
    		{
    			int32 data = q.front();
    			q.pop();
    
    			// lock 걸고 출력하는 게 좋은 건 아니지만 학습을 위해			
    			cout << data << endl;
    		}
    	}
    }
    
    int main()
    {
    	handle = CreateEvent(nullptr/*보안속성*/, false/*bManualReset*/, false/*bInitialState*/, nullptr);
    
    	thread t1(Producer);
    	thread t2(Consumer);
    
    	t1.join();
    	t2.join();
    
    	CloseHandle(handle);
    }

    두 줄이 추가면서 CPU가 전보다 효율적으로 작동합니다.

    0%

    댓글

Designed by Tistory.