이벤트 제어 c/c++

Drakk·2021년 9월 10일
1
post-thumbnail

🔪소개및 개요

💎개요

안녕하세요!!
오랜만에 벨로그에다가 글씁니다.
사실 티스토리블로그랑 같이쓰기귀찮아서
한동안 티스토리에만 올렸습니다..ㅋㅋㅋ

이번에는 C와 C++에서 각각 이벤트를 제어하는 방법에 관하여
C스타일과 C++스타일을 각각 따로따로 알아보도록하겠습니다.

💎환경

IDE > DevCpp
Lang > C98, C++11, C++20
OS > Windows10 Home
Compiler > MINGW g++

🔪실습

💎설명

병렬프로그래밍을 하다보면...
가끔 특정조건이 만족될때, 다른스레드에서 이벤트를 수행하도록 하고싶을때가 있습니다. (코루틴과 비슷)

이번글에서는
WinAPI(c98),
std::condition_variable(c++11),
std::binary_semaphore(c++20) 를 이용하여 각각,
C스타일과 C++스타일을 나눠서 설명하겠습니다.

코드는 이번강좌를 위해 즉석으로 직접 짰습니다.

💎소스코드 (C스타일 - WinAPI)

우선 C에서는 C++에서처럼 스레드를 생성할때 한번에 여러개의 파라미터를 주는것이 불가능합니다.

따라서 저는 구조체를 하나만들겠습니다.

이벤트핸들에 관한 정보가 담긴 구조체

/**
		@struct PER_HANDLE
*/
typedef struct {
	HANDLE pHandle; /* 이벤트를 다룰 핸들입니다. :D */
	DWORD TIME; /* 이벤트 타임아웃 시간입니다. xD */
}PER_HANDLE, *LPPER_HANDLE;

그리고 아래 소스를 모두 이해하기 위해서 각각 함수의 역할을 간략하게 설명하겠습니다.

//< fn
CreateThread > 스레드 생성
CreateEvent > 이벤트 생성
SetEvent > 이벤트를 singal, non-signal상태로 바꾼다.
WaitForSingleObject > 단일오브젝트를 해당시간만큼 기다린다. (std::thread로 비교하자면 시간설정이 가능한 thread::join)

//< macro
WAIT_OBJECT_0 > 오브젝트가 signal상태가 됨.
WAIT_TIMEOUT > 오브젝트 타임아웃

다음은 전체 소스입니다.

전체소스

#include <conio.h> //< _getch()
#include <windows.h> //< Event Functions...
#include <stdio.h> //< printf()

/**
		@struct PER_HANDLE
*/
typedef struct {
	HANDLE pHandle; /* 이벤트를 다룰 핸들입니다. :D */
	DWORD TIME; /* 이벤트 타임아웃 시간입니다. xD */
}PER_HANDLE, *LPPER_HANDLE;


/**
		@brief 이벤트스레드
*/
DWORD __stdcall EventThread(LPVOID hHandle){
	LPPER_HANDLE pHandle = (LPPER_HANDLE)(hHandle);
	
	printf("TEST (%d)\n", 1);
	
	/**
			@brief 이벤트 생성 
	*/
	pHandle->pHandle = CreateEvent(0, TRUE, FALSE, 0);
	
	printf("TEST (%d)\n", 2);
	
	/**
			@brief 이벤트가 signal상태가 될때까지 기다린다.
	*/
	DWORD dwRet = WaitForSingleObject(pHandle->pHandle, pHandle->TIME);
	
	/**
			@brief 이벤트가 signal상태 확인완료 
	*/
	if(dwRet == WAIT_OBJECT_0)
        printf("TEST (%d)\n", 3);
        
    	/**
			@brief 이벤트 타임아웃 (재시간내에 이벤트가 signal상태가 되지못함) 
	*/
    else if(dwRet == WAIT_TIMEOUT)
        printf("TIMEOUT\n");

    CloseHandle(pHandle->pHandle);
	
	printf("TEST (%d)\n", 4);
	
	return (0);
}

int main() {
	LPPER_HANDLE 		Handle;
	Handle = 		(LPPER_HANDLE)malloc(sizeof(PER_HANDLE));
	
	Handle->TIME = 		INFINITE;
	Handle->pHandle = 	NULL;
	
	/**
			@brief 이벤트스레드 생성
			@fn EventThread -> __stdcall  콜백함수
	*/
	HANDLE pThread = CreateThread(0, 0, EventThread, (LPVOID)Handle, 0, 0);
	
	printf("Click The Any Keys (%d)\n", 1);
	_getch();
	
	
	/**
			@brief 해당 이벤트를 signal상태로 바꾸기 
	*/
	SetEvent(Handle->pHandle);
	
	printf("Click The Any Keys (%d)\n", 2);
	_getch();
	
	WaitForSingleObject(pThread, INFINITE);
	CloseHandle(pThread);
	
	free(Handle);
	
	return (0);
}

그리고 실행화면!

보시다시피 핸들이 singal상태가 됬습니다.
그후 이벤트스레드에서는 저가 기대하던 "WAIT_OBEJECT_0"분기가 수행되었습니다.



💎소스코드 (C++스타일 - condition_variable)

다음은 C++스타일인데 우선 condition_variable 사용할겁니다.

설명은 주석으로 해놓았습니다.
저가 주석으로 곳곳에 #Point로 프로그램이 실행되는 순서를 적어놨습니다.

#include <condition_variable> //< condition_variable
#include <mutex> //< std::mutex | std::unique_lock
#include <thread> //< std::thread
#include <iostream> //< std::cout

#include <conio.h> //< _getch()

void EventFuntion(std::condition_variable* cv, std::mutex* mtx, bool& MainFlag, bool& EventFlag){
	std::unique_lock<std::mutex> locker(*mtx);
	std::cout << "Event #" << 1 << '\n';
	
	/**
			@brief 메인함수쪽 실행 #Point-M1 
	*/
	MainFlag = true;
	cv->notify_one();
	
	/**
			@brief EventFlag가 참이 될때 wait끝내기 #Point-E1
	*/
	cv->wait(locker, [&]() { return EventFlag; });
	
	std::cout << "Event #" << 2 << '\n';
}

int main(){
	bool MainFlag = false, EventFlag = false;
	std::condition_variable cv;
	std::mutex mtx;
	
	std::thread EventThread(EventFuntion, &cv, &mtx, std::ref(MainFlag), std::ref(EventFlag));
	
	/**
			@brief 해당스코프에서만 lock #Point-M1
	*/
	{
		std::unique_lock<std::mutex> locker(mtx);
		cv.wait(locker, [&]() { return MainFlag; });
	}
	
	std::cout << "Input the Any Keys #" << 1 << '\n';
	_getch();
	
	/**
			@brief Event함수쪽 실행 #Point-E1 
	*/
	EventFlag = true;
	cv.notify_one();
	
	EventThread.join();
	
	/**
			@brief 초기화 (클래스일경우 소멸자에다가 넣기!) 
	*/
	MainFlag = false;
	EventFlag = false;
	cv.notify_all();
}

실행화면입니다.

race condition이 발생하지않고 정확하게 저가 원하는 출력결과가 항상나옵니다!



💎소스코드 (C++스타일 - binary_semaphore)

이번에는 세마포어를 이용한 이벤트 제어입니다.
사실 위 방법처럼 condition_variable을 사용하면은 되게 귀찮고 까다롭습니다.

따로 플래그도 조절해야되고, mutex를 어디까지 lock해야하나.. 등등..

세마포어를 사용하면 위 과정보다 훨씬 쉽고 편하게 이벤트제어가 가능합니다.

#include <semaphore> //< std::binary_semaphore
#include <thread> //< std::thread
#include <iostream> //< std::cout

#include <conio.h> //< _getch()

void EventFunction(std::binary_semaphore* MainHandler, std::binary_semaphore* EventHandler){
	std::cout << "Event #" << 1 << '\n';
	MainHandler->release(); /*< MainHandler, 언블럭킹전환 >*/
	EventHandler->acquire(); /*< EventHandler, 블럭킹전환 >*/
	
	std::cout << "Event #" << 2 << '\n';
}

int main() {
	std::binary_semaphore MainHandler(0);
	std::binary_semaphore EventHandler(0);
	
	std::cout << "Main #" << 1 << '\n';
	std::thread EventThread(EventFunction, &MainHandler, &EventHandler);
	MainHandler.acquire(); /*< MainHandler, 블럭킹전환 >*/
	
	std::cout << "Input the Any Keys #" << 1 << '\n';
	_getch();
	EventHandler.release(); /*< EventHandler, 언블럭킹전환 >*/
	
	EventThread.join();
	
	std::cout << "Main #" << 2 << '\n';
}

실행화면입니다.

자... race condition이 발생하지않았고, 이 역시 프로그래머가 직접 이벤트제어를 하기 편합니다.
확실히 위 condition_variable을 사용했을때보다 더 간단하고 사용하기 편리합니다.. 그죠??



🔪마치며...

이번 포스팅에서는 다양하게 이벤트를 제어하는 방법에 관하여 써보았습니다.
궁금한 부분있으시면 댓글로 질문주세요!

profile
C++ / Assembly / Python 언어를 다루고 있습니다!

0개의 댓글