안녕하세요!!
오랜만에 벨로그에다가 글씁니다.
사실 티스토리블로그랑 같이쓰기귀찮아서
한동안 티스토리에만 올렸습니다..ㅋㅋㅋ
이번에는 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에서는 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 사용할겁니다.
설명은 주석으로 해놓았습니다.
저가 주석으로 곳곳에 #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이 발생하지않고 정확하게 저가 원하는 출력결과가 항상나옵니다!
이번에는 세마포어를 이용한 이벤트 제어입니다.
사실 위 방법처럼 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을 사용했을때보다 더 간단하고 사용하기 편리합니다.. 그죠??
이번 포스팅에서는 다양하게 이벤트를 제어하는 방법에 관하여 써보았습니다.
궁금한 부분있으시면 댓글로 질문주세요!