[C++ 서버] Event

이정석·2023년 11월 15일

CppServer

목록 보기
4/8

Event

동기화 메커니즘 중 하나도 Event를 사용할 수 있는데 [C# 서버] Event에서 Event를 이용한 동기화 방법에 대해서 다뤄본적이 있다. C#과 다른점이라면 C#은 AutoResetEvent, ManualResetEvent로 따로 생성해줄 수 있었지만 C++에는 파라미터로 지정해주어야 한다.

1. 기본코드

아래 코드는 두 개의 쓰레드가 공유자원인 Queue에 각각 push, pop하는 구조를 가지는 코드이다. 두 함수는 Queue에 접근하기 전에 mutex를 이용해 Queue 다른 쓰레드가 접근하는 것을 방지하고 연산을 진행한다.

  mutex m;
  queue<int32> q;

  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 (q.empty() == false)
          {
              int32 data = q.front();
              q.pop();
              cout << data << endl;
          }
      }
  }

  int main() {
      thread t1(Producer);
      thread t2(Consumer);

      t1.join();
      t2.join();

      return 0;
  }

위 코드에서 만약 Producer가 100ms가 아니라 100초, 1000초마다 push한다면? Consumer는 100초동안 계속 확인하면서 CPU자원을 낭비하게 될 것이다.

Event로 이 문제를 해결할 수 있다. Producer는 push Event의 Signal을 활성화시켜 Consumer 쓰레드에게 알리는 방법

mutex m;
queue<int32> q;
HANDLE handle;

void Producer() {
	while (true) {
		{
			unique_lock<mutex> lock(m);
			q.push(100);
		}

		::SetEvent(handle);
		this_thread::sleep_for(100ms);
	}
}

void Consumer() {
	while (true) {
		::WaitForSingleObject(handle, INFINITE);

		unique_lock<mutex> lock(m);
		if (q.empty() == false)
		{
			int32 data = q.front();
			q.pop();
			cout << data << endl;
		}
	}
}

int main() {
	handle = ::CreateEvent(NULL, FALSE, FALSE, NULL);

	thread t1(Producer);
	thread t2(Consumer);

	t1.join();
	t2.join();

	::CloseHandle(handle);

	return 0;
}

SpinLock은 유저단계에서 일어나는 동기화 기법이라면 Event는 커널단계까지 개입하는 방법이라고 할 수 있다. 결국 커널모드로의 전환이 이루어지기 때문에 Lock과정에서 이뤄지는 작업이 짧은 단위로 끝난다면 SpinLock이 더 효울적일수도 있다. 결국, 상황에 따라 맞는 방법을 선택하는 것이 가장 중요하다고 생각한다.

2. Event, Handle

Event를 만들고, 기다리고, 상태를 변경함으로 쓰레드간의 동기화가 가능하다. Event를 다루는 대표적인 방법은 아래와 같다.

  • 생성: CreateEvent로 Event를 생성할 수 있고, 식별하기 위한 Handle을 반환한다.
  • 상태: SetEvent로 Signal을 활성화 시킬 수 있고, ResetEvent로 비활성화 시킬 수 있다.
  • 대기: WaitForSingleObject, WaitForMultipleObjects로 Event가 Signal될 때까지 대기상대로 들어갈 수 있다. 함수 이름을 보면 하나를 기다리거나 여러개를 기다릴 수 있다.
  • 종료: CloseHandle로 생성된 Handle을 닫음으로 커널 오브젝트를 해제할 수 있다.

Event를 생성할 때 반환값으로 Handle을 반환한다. Handle은 Signal을 활성화 시킬 때 Handle을 같이 넘겨줌으로 커널에서 어떤 Event를 활성화시킬지 식별할 수 있도록 해준다.

AutoResetEvent는 Wait에서 깨어난 후에 자동으로 Evnet를 Reset시켜주고 하나의 쓰레드만 깨어나는 것을 보장할 수 있다. 하지만 ManualResetEvnet는 수동으로 Reset 함수를 호출해주어야 된다. 즉, Event가 Signal 상태가 되었을 때 깨우는 쓰레드의 개수에 차이가 있다.

profile
게임 개발자가 되고 싶은 한 소?년

0개의 댓글