원작자: Walter Capers
작성일: 2007. 11. 08
평점: 4.73 / 5.00 (28명 평가)
원문: Creating a C++ Thread Class (Code Project) / A look at platform independent threading in C++ (역: C++ 쓰레드 클래스 만들기 / C++로 플랫폼에 의존하지 않는 쓰레드 클래스 만들기를 살펴봅시다)
Introduction
최근에, 제 동생이 객체 지양적인 쓰레딩을 위한 C++ 클래스를 쉽게 만드는 방법에 대해서 물어봤었습니다. 저는 과거에 많은 멀티쓰레드 라이브러리를 작성했지만 그건 모두 C 였습니다. 항상 저는 저수준 프로그래밍언어인 C를 선택했었고 C++은 GUI 개발을 위해서만 사용했었습니다. 비록 Code Project에 객체 지향 쓰레딩에 대한 훌륭한 예제들이 많이 있지만 제 동생이 필요로 하는, 제 입맛에 맞는 클래스 구현에 대한건 없었죠. 제 동생은 아래 특징을 갖춘 쓰레드 클래스를 만들기를 원했습니다.
1. 비동기 쓰레딩에 기반한 이벤트 방식과 Tick모두 지원해야 합니다.
2. 균질 쓰레딩과 특수화된 쓰레딩 모두 지원해야 합니다.
3. 여러 작업들을 요청하고 처리하기 위해 FCFS(처음 들어온게 처음 나가는) 큐를 지원해야 합니다.
4. 가벼워야 합니다.
5. 간단하게 구현되어야 합니다.
새로운 클래스인 CThread를 지원하기 위해서 다른 보조 클래스들도 개발했습니다. 이런 것들 중에는 CMutexClass와 CEventClass, CTask 클래스들이 포함됩니다. CTask 클래스는 기존과 같은 기능을 하는 비동기 쓰레딩을 구현하길 원하는 클래스의 기반 클래스가 되고, CMutexClass나 CEventClass는 리소스 관리를 보조합니다.
What is Threading?
모든 프로세스들은 적어도 한개의 제어 쓰레드를 가지고 있으며 적어도 한번에 하나의 작업을 수행할 수 있습니다. 우리는 하나 이상의 제어 쓰레드를 가지고 있는 프로세스를 멀티 쓰레딩 프로세스라고 정의 하지요. 멀티 쓰레딩 프로세스는 별개의 프로세스 없이 다수의 비동기 작업을 수행할 수 있습니다.
Resource Management-Thread Synchronization
멀티 쓰레딩 프로세스는 같은 리소스를 여러 쓰레드가 공유하고 있기 때문에 OS 수준에서의 데이터 무결성을 보장하기 위한 제어 메커니즘이 필수적입니다. 두개의 쓰레드가 동시에 같은 변수를 수정하려고 하거나 어떤 쓰레드가 변수의 값을 읽고 있는데 그 값을 다른 쓰레드에서 변경하게 되면 데이터 무결성을 보장하기 힘들게 됩니다. 이런 경우를 막기 위해서 운영체제는 상호 배제를 지원하는 개체를 제공하며, 이건 짧게 mutex(역자 주: 이하 뮤텍스라고 하겠습니다)라고 알려졌죠. 멀티 쓰레딩을 활용하는 어플리케이션에서 뮤텍스는 다수의 쓰레드가 동시에 같은 리소스에 접근하는 것을 차단해줍니다. 어떤 쓰레드가 리소스에 접근할 필요가 있을 때 그 쓰레드는 가장 먼저 뮤텍스를 획득하는 작업을 수행합니다. 뮤텍스가 이미 한번 누군가에게 주어지면 다른 쓰레드가 같은 뮤텍스를 얻으려는 시도를 할 때 흐름을 차단하고, 해당 쓰레드를 CPU 연산 능력을 적게 사용하는 대기 상태로 진입시켜버립니다. 쓰레드가 데이터 접근을 완료하면, 그 쓰레드는 자신이 갖고 있던 뮤텍스를 풀어주는데 이건 다른 쓰레드가 그 뮤텍스를 얻을 수 있도록 만들어 주게 됩니다. 그러고 나면 그 데이터에 접근할 수 있게 되겠지요.
주의할 점은 뮤텍스를 잘못 사용한 구현은 데드락이라고 불리는 리소스 결핍을 발생시킬 수 있습니다. 리소스 결핍은 하나 또는 그 이상의 쓰레드가 같은 리소스를 가지고 경쟁을 벌일 때 일어납니다.
예를 들면,
(역자 주: 이 부분에 대해서 원문의 설명이 부족한 것 같아 이미지만 가져왔습니다. 출처: http://javarevisited.blogspot.kr/2010/10/what-is-deadlock-in-java-how-to-fix-it.html)
위 예에서 데드락이 발생했는데, 쓰레드 2번이 가지고 있던 개체 2번을 쓰레드 1번이 획득하려는 시도를 하면서 그 쓰레드의 흐름이 차폐되었습니다. 그런데 쓰레드 2번 또한 쓰레드 1번이 가지고 있는 개체 1번을 획득하려고 시도하면서 두개 모두 차단되어 데드락이 발생한 것입니다.
UNIX는 뮤텍스 처럼 상태 변수라는 동기화 메커니즘의 또다른 형태를 가지고 있습니다. 상태 변수는 쓰레드가 서로 의견을 나눌 수 있도록 해주는데 그것들은 어떤 쓰레드가 상태 변수의 값을 바꾸면 다른 쓰레드에게 그게 변경되었다고 알려주는 동작을 수행합니다. 윈도우즈에서는 이걸 이벤트라는 형태로 제공하죠.
Operating System Calls
아래 목록은 CMutexClass, CEventClass, CTask 그리고 CThread를 구현하는데 사용된 함수들입니다.
#. 함수명 - 타깃 플랫폼 - 설명 - 사용한 클래스
1. CreateThread - Windows - 윈도우 쓰래드를 생성합니다. - CThread
2. pthread_create - UNIX POSIX - UNIX 쓰레드를 생성합니다. - CThread
3. pthread_join - UNIX POSIX - UNIX 쓰레드가 종료될 때 까지 대기합니다 - CThread
4. pthread_attr_init - UNIX POSIX - 쓰레드 속성 구조의 값을 기본값으로 초기화합니다. - CThread
5. pthread_attr_setstacksize - UNIX POSIX - 쓰레드 속성 구조체의 스택 크기를 변경합니다. - CThread
6. WaitForSingleObject - Windows - 어떤 이벤트가 Signaled 상태로 설정 될 때 까지 대기합니다. - CThread, CMutexClass, CEventClass
7. CreateMutex - Windows - 네임드 혹은 언네임드 뮤텍스를 생성합니다. - CMutexClass
8. CloseHandle - Windows - 윈도우즈 핸들로서 할당된 리소스를 해제합니다. - CMutexClass, CEventClass, CThread
9. ReleaseMutex - Windows - 이전에 WaitForSingleObject로 획득한 뮤텍스 락을 해제합니다. - CMutexClass, CEventClass
10. pthread_mutexattr_init - UNIX POSIX - 속성 구조체를 초기화시킵니다. - CMutexClass, CEventClass
11. pthread_mutex_init - UNIX POSIX - 제공된 속성 구조체를 이용하여 뮤텍스를 초기화시킵니다. - CMutexClass, CEventClass
12. pthread_mutex_lock - UNIX POSIX - 뮤텍스를 잠급니다. - CMutexClass, CEventClass
13. pthread_mutex_unlock - UNIX POSIX - 잠겨있던 뮤텍스를 풀어줍니다. - CMutexClass, CEventClass
14. pthread_mutex_destroy - UNIX POSIX - 뮤텍스에 할당된 리소스를 해제합니다. - CMutexClass, CEventClass
15. CreateEvent - Windows - 윈도우즈 이벤트 개체를 생성합니다. - CEventClass
16. SetEvent - Windows - 윈도우즈 이벤트를 Signaled 상태로 설정합니다. - CEventClass
17. pthread_cond_signal - UNIX POSIX - 블로킹된 쓰레드를 풀어줍니다. - CEventClass
18. pthread_cond_wait - UNIX POSIX - 상태 변수를 잠급니다. - CEventClass
19. pthread_cond_init - UNIX POSIX - 상태 변수를 초기화시킵니다. - CEventClass
(역자 주: 원글에서는 이걸 표로 나타내고 있는데, 모바일 가독성이 떨어져서 이렇게 표시하였습니다)
The CEventClass Class
CEventClass 클래스는 윈도우즈 이벤트 함수나 이벤트 객체를 캡슐화 하였고, UNIX에서 동일한 동작을 제공하기 위해서 상태 변수와 그 함수를 캡슐화한 구현을 제공합니다. 이 클래스 안에 포함된 메서드들은 SetEvent와 CreateEvent의 개념을 따서 만들었으며, 유닉스에서 pthread_cond_init, pthread_cond_destroy, pthread_cond_signal 그리고 pthread_cond_wait 함수를 통해 동일한 동작을 구현하였습니다. 이벤트 동기화 개체들은 UNIX 환경에서는 상태 변수라고 불리는데 구현의 명료함을 위해서 윈도우즈 환경의 이벤트 객체 개념으로서 두가지 모두를 참조하였습니다.
Member Methods
1. void Set() - 이 함수는 이벤트를 Signaled 상태로 만들고, 이 이벤트를 기다리고 있는 쓰레드를 깨웁니다.
2. bool Wait() - 이 함수는 이 이벤트가 Signaled 상태가 되기 전까지 블로킹(흐름을 차단) 시킵니다. 이게 TRUE를 반환하면 성공이고, FALSE를 반환하면 실패햇음을 의미합니다.
3. void Reset() - 이 함수는 이벤트를 Unsignaled 상태로 리셋시킵니다.
이벤트 수신 쓰레드에서 이벤트 개체를 사용한 예제:
while(bContinueRunning) {
event.Wait(); // 이벤트가 발생하기를 기다립니다.
// 어떤 작업을 수행합니다.
event.Reset(); // 이벤트를 Unsignaled 상태로 리셋시킵니다.
}
신호를 주는 쓰레드에서 사용하는 예제:
// 어떤 데이터를 바꾸고,
event.Set(); // 데이터가 바뀌었다고 이벤트를 통해서 알려줌.
The CTask Class and Non-Specialized Threads
내가 본 많은 쓰레드 프로그래밍 예제에서는 쓰레드에 의해 처리될 데이터를 전역 변수로 선언하고, 그걸 뮤텍스로 보호했습니다. 데이터를 처리하는 코드들이 쓰레드 함수 내부에 모두 통합되어 있었죠. 저는 이런 형태를 특수화된 비동기 쓰레딩이라고 정의합니다. 이상적이게도, 데이터와 기능에 따라 처리될 데이터가 같은 개체에 캡슐화되어 있는 경우도 있는데 저는 이런 형태의 쓰레딩을 균질 비동기 쓰레딩(HAT)이라고 정의합니다. HAT에서 쓰레드들은 특수화되어 있지 않기 때문에 출력 쓰레드와 I/O 쓰레드가 따로 존재하지 않습니다. 대신에 단일 쓰레드가 두가지 모두를 처리할 수 있습니다. 왜냐하면 그 작업들이 완성된 개체로서 구현되어 있기 때문입니다. 그렇기 때문에 데이터를 처리하기 위해 필요한 기능과 데이터 모두를 포함하고 있었습니다. CTask 클래스는 그런 HAT 쓰레딩을 위한 기반 클래스입니다.
typedef enum {
TaskStatusNotSubmitted,
TaskStatusWaitingOnQueue,
TaskStatusBeingProcessed,
TaskStatusCompleted } TaskStatus_t;
class CTask {
private:
CMutexClass m_mutex;
TaskStatus_t m_state;
ThreadId_t m_dwThread;
public:
void SetTaskStatus(TaskStatus_t state) {
m_mutex.Lock();
m_state=state;
m_mutex.Unlock();
}
void SetId(ThreadId_t *pid) {
memcpy(&m_dwThread, pid, sizeof(ThreadId_t));
}
/**
*
* Wait
* 작업이 완료될 때 까지 대기합니다.
* 그러나 지정된 시간이 되어도 완료되지 않으면 FALSE를 반환하면서
* 탈출하게 됩니다.
*
**/
BOOL Wait(int timeoutSeconds) {
timeoutSeconds = timeoutSeconds * 1000;
if( Status() != TaskStatusCompleted && timeoutSeconds > 0 ) {
Sleep(100);
timeoutSeconds = timeoutSeconds - 100;
}
if( Status() == TaskStatusCompleted ) return TRUE;
return FALSE;
}
/**
*
* Where
* 이 작업의 상태를 반환합니다.
*
**/
TaskStatus_t Status() {
TaskStatus_t state ;
m_mutex.Lock();
state = m_state;
m_mutex.Unlock();
return state;
}
void Thread(ThreadId_t *pId) {
memcpy(pId,&m_dwThread,sizeof(ThreadId_t));
}
CTask(){m_state=TaskStatusNotSubmitted;
memset(&m_dwThread,sizeof(ThreadId_t),0); }
~CTask(){}
virtual BOOL Task()=0; // 이 작업이 수행해야 할 작업을 구현하는 추상 메서드입니다.
};
Member Methods
1. virtual BOOL Task() - 이 함수는 CThread 개체가 작업을 수행하기 위해서 호출하는 함수입니다.
2. TaskStatus_t Status() - 이 함수는 작업의 상태를 나타내는데, TaskStatusNotSubmitted, TaskStatusWaitingOnQueue, TaskStatusBeingProcessed, TaskStatusCompleted 중 하나의 값을 가지게 됩니다.
3. void Thread(ThreadId_t* pid) - 이 함수는 이 작업을 실행하고 있는 쓰레드의 ID를 반환합니다.
4. BOOL Wait(int iTimeInSeconds) - 이 함수는 작업이 완료될 때 까지 대기합니다. 그러나 지정된 시간이 되어도 완료되지 않으면 FALSE를 반환하면서 탈출하게 됩니다.
(역자 주: 외부에서의 접근을 허용치 않는 멤버 필드를 제거했습니다만, 원문에서는 여기에 그거에 대한 것들도 포함하고 있습니다)
The CThread Class, Putting It All Together
Member Methods
1. BOOL Event(LPVOID lpvData) - 어떤 데이터 블럭을 이벤트 스택(혹은 큐)에 등록하고, 쓰레드에게 처리할 데이터가 기다리고 있다고 알려줍니다.
2. BOOL Event(CTask* pTask) - CTask 개체를 이벤트 스택(혹은 큐)에 등록하고 그게 처리되어야 한다고 쓰레드에게 알려줍니다.
3. int GetEventsPending() - 이벤트 스택에서 대기중인 이벤트의 수를 반환합니다.
4. ThreadId_t GetId() - 이 쓰레드의 ID를 반환합니다.
5. DWORD GetErrorFlags() - 이 개체의 에러 플래그를 반환하는데, 에러가 없으면 0이 반환(NO_ERRORS)될것이고 에러가 있으면 EVENT_CREATION, MUTEX_CREATION, THREAD_CREATION, ILLEGAL_USE_OF_EVENT 중 하나 혹은 그 이상이 설정되어 있을 것입니다.
6. BOOL PingThread(DWORD dwTimeoutMilli) - 쓰레드가 현재 가동중인지 아닌지를 결정하며, TRUE를 반환하면 가동중, 아니라면 가동중이 아니라는 뜻입니다.
7. SetPriority(DWORD dwPriority) - 이 함수는 윈도우즈 전용이며, 쓰레드의 우선순위를 변경합니다.
8. BOOL Start() - 이 쓰레드를 시작시킵니다.
9. BOOL Stop() - 이 쓰레드를 정지시킵니다.
10. void SetIdle(DWORD dwIdle) - 이 쓰레드가 몇 ms 단위로 작업을 수행할지 결정합니다. (Tick 기반 쓰레딩에서 사용합니다)
11. SetThreadType(ThreadType_t type, DWORD dwIdle) - ThreadTypeEventDriven 혹은 ThreadTypeIntervalDriven 중 하나로 쓰레드 종류를 설정합니다.
12. ThreadState_t ThreadState() - 쓰레드의 상태를 반환하며, 반환값은 ThreadStateBusy(지금 이벤트를 처리하고 있음), ThreadStateWaiting(이벤트 대기중), ThreadStateDown(가동중이 아님), ThreadStateShutingDown(쓰레드가 중단되고 있음) 중 하나입니다.
class CIncrementThread : public CThread {
public:
int counter;
virtual BOOL OnTask( LPVOID lpv ) {
ThreadId_t id;
GetId(&id);
if( lpv ) {
int *pInt = (int *)lpv;
// 여기서 cout을 사용하지 말아주세요. 그걸 쓰게되면 출력이 뒤죽박죽 썪이게 될겁니다.
printf("\tthread(%ld, counter+%d=%d, counter incremented\n", id,*pInt,(counter+=*pInt));
}
return TRUE;
}
virtual BOOL OnTask() {
ThreadId_t id;
GetId(&id);
// 여기서 cout을 사용하지 말아주세요. 그걸 쓰게되면 출력이 뒤죽박죽 썪이게 될겁니다.
m_mutex.Lock(); // 카운터를 바꾸는 동작을 하기 위해서...
printf("\tthread(%ld, counter++= %d, counter incremented)\n", id,(++counter));
m_mutex.Unlock();
return TRUE;
}
int GetValue() {
int counterValue = 0;
m_mutex.Lock(); // protect the counter variable
counterValue = counter;
m_mutex.Unlock();
return counter;
}
void Reset() {
m_mutex.Lock();
counter = 0;
m_mutex.Unlock();
}
CIncrementThread(){counter=0;}
~CIncrementThread(){}
};
int main( int argc, char *argv[]) {
// 개체가 할당되자마자 쓰레드가 시작될 것입니다.
CIncrementThread MyThread;
int two=2;
while( MyThread.GetValue() < 20 ) {
MyThread.Event(); // 값을 1씩 증가시킵니다.
Sleep(100); // 메인 쓰레드를 100ms 재웁니다.
}
MyThread.Reset();
while( MyThread.GetValue() < 40 ) {
MyThread.Event(&two);
Sleep(100);
}
}
출력은 아래처럼 나올겁니다.
thread(5220, counter++= 1, counter incremented)
thread(5220, counter++= 2, counter incremented)
thread(5220, counter++= 3, counter incremented)
thread(5220, counter++= 4, counter incremented)
thread(5220, counter++= 5, counter incremented)
thread(5220, counter++= 6, counter incremented)
thread(5220, counter++= 7, counter incremented)
thread(5220, counter++= 8, counter incremented)
thread(5220, counter++= 9, counter incremented)
thread(5220, counter++= 10, counter incremented)
thread(5220, counter++= 11, counter incremented)
thread(5220, counter++= 12, counter incremented)
thread(5220, counter++= 13, counter incremented)
thread(5220, counter++= 14, counter incremented)
thread(5220, counter++= 15, counter incremented)
thread(5220, counter++= 16, counter incremented)
thread(5220, counter++= 17, counter incremented)
thread(5220, counter++= 18, counter incremented)
thread(5220, counter++= 19, counter incremented)
thread(5220, counter++= 20, counter incremented)
thread(5220, counter+2=2, counter incremented
thread(5220, counter+2=4, counter incremented
thread(5220, counter+2=6, counter incremented
thread(5220, counter+2=8, counter incremented
thread(5220, counter+2=10, counter incremented
thread(5220, counter+2=12, counter incremented
thread(5220, counter+2=14, counter incremented
thread(5220, counter+2=16, counter incremented
thread(5220, counter+2=18, counter incremented
thread(5220, counter+2=20, counter incremented
thread(5220, counter+2=22, counter incremented
thread(5220, counter+2=24, counter incremented
thread(5220, counter+2=26, counter incremented
thread(5220, counter+2=28, counter incremented
thread(5220, counter+2=30, counter incremented
thread(5220, counter+2=32, counter incremented
thread(5220, counter+2=34, counter incremented
thread(5220, counter+2=36, counter incremented
thread(5220, counter+2=38, counter incremented
thread(5220, counter+2=40, counter incremented
위 예제를 보면, 저는 CIncrementThread 클래스를 CThread 클래스를 상속받아서 구현했습니다. 클래스 선언에서 저는 OnTask메서드를 재정의했고, 그 메서드에서 카운터를 하나씩 증가시켰습니다. 이 예제는 한개의 쓰레드가 두종류의 이벤트를 처리할 수 있다는 걸 보여줍니다. 왜냐하면 카운터 변수가 둘 이상의 쓰레드에 의해 접근될 가능성이 있기 때문에 CThread::m_mutex 개체를 이용하여 하나의 쓰레드에서만 접근할 수 있도록 만들었습니다.
HAT (균질 비동기 쓰레딩) 쓰레드들은 CThread와 CTask 클래스 모두를 사용하도록 구현되어 있습니다.
class CTaskIncrementer: public CTask {
private:
int counter;
int incr;
public:
void SetIncr(int iValue) {
m_mutex.Lock();
incr = iValue;
m_mutex.Unlock();
}
int GetIncrementValue() {
int incrValue;
m_mutex.Lock();
incrValue=incr;
m_mutex.Unlock();
return incrValue;
}
int GetValue() {
int counterValue = 0;
m_mutex.Lock(); // protect the counter variable
counterValue = counter;
m_mutex.Unlock();
return counter;
}
BOOL Task() {
ThreadId_t id;
Thread(&id);
m_mutex.Lock();
printf("\tthread(%ld, counter+%d=%d, counter incremented\n", id,incr,(counter+=incr));
m_mutex.Unlock();
return TRUE;
}
CTaskIncrementer(){counter=0;}
~CTaskIncrementer(){}
};
int main(int argc, char *argv[]) {
CTaskIncrementer incr;
CThread thr;
incr.SetIncr(2);
while( incr.GetValue() < 40 ) thr.Event(&incr);
}
위 예제의 결과는 아래처럼 나올 것입니다.
thread(5700, counter+2=2, counter incremented
thread(5700, counter+2=4, counter incremented
thread(5700, counter+2=6, counter incremented
thread(5700, counter+2=8, counter incremented
thread(5700, counter+2=10, counter incremented
thread(5700, counter+2=12, counter incremented
thread(5700, counter+2=14, counter incremented
thread(5700, counter+2=16, counter incremented
thread(5700, counter+2=18, counter incremented
thread(5700, counter+2=20, counter incremented
thread(5700, counter+2=22, counter incremented
thread(5700, counter+2=24, counter incremented
thread(5700, counter+2=26, counter incremented
thread(5700, counter+2=28, counter incremented
thread(5700, counter+2=30, counter incremented
thread(5700, counter+2=32, counter incremented
thread(5700, counter+2=34, counter incremented
thread(5700, counter+2=36, counter incremented
thread(5700, counter+2=38, counter incremented
thread(5700, counter+2=40, counter incremented
Tick 기반 쓰레드는 미리 정의된 간격에 맞춰서 쓰레드가 깨어나게 되고, 환경이 변경되었는지를 확인한 뒤에 변경된 것들을 처리합니다. 다음 간격만큼 자고 일어나서 또다시 변경된 것들을 처리합니다. 이걸 구현하려면 CThread를 상속받은 후 OnTask(LPVOID)를 재정의 해야합니다. 이 쓰레드가 인스턴싱된 후에 SetThreadType 메서드를 이용해서 ThreadTypeIntervalDriven을 지정해 주고, 간격을 ms로 지정해줘야 합니다.
class CIncrementThread : public CThread {
public:
int counter;
virtual BOOL OnTask() {
ThreadId_t id;
GetId(&id);
m_mutex.Lock(); // protect the counter variable
printf("\tthread(%ld, counter++= %d, counter incremented)\n", id,(++counter));
m_mutex.Unlock();
return TRUE;
}
int GetValue() {
int counterValue = 0;
m_mutex.Lock(); // protect the counter variable
counterValue = counter;
m_mutex.Unlock();
return counter;
}
void Reset() {
m_mutex.Lock();
counter = 0;
m_mutex.Unlock();
}
CIncrementThread(){counter=0;}
~CIncrementThread(){}
};
int main( int argc, char *argv[]) {
CIncrementThread thr;
thr->SetThreadType(ThreadTypeIntervalDriven,100);
Sleep(500);
}
위 예제의 결과는 아래와 같습니다.
thread(6104, counter++= 12, counter incremented)
thread(6104, counter++= 13, counter incremented)
thread(6104, counter++= 14, counter incremented)
thread(6104, counter++= 15, counter incremented)
thread(6104, counter++= 16, counter incremented)
Conclusion
독자분께서 구현하신 것은 완전히 구현된 쓰레드 개체입니다. 저는 이걸 리눅스에서 테스트 해봤는데 잘 동작했고, 아직 SunOS나 다른 UNIX 플랫폼에서 정상적으로 동작하는지는 테스트하지 못했습니다. Windows에서 컴파일 할 때 주의해야 할 점은, /Mt 옵션이나 /Mtd 옵션을 코드 생성 옵션으로 줘야 합니다. 이것은 독자분의 어플리케이션이 멀티 쓰레딩 어플리케이션인지 아닌지를 알려주는 동작을 합니다. 리눅스에서 이걸 컴파일하려면 아래 Makefile을 사용하시면 됩니다.
CC=g++
LIBS=-lpthread -lrt
CFLAGS=-DLINUX -DNANO_SECOND_SLEEP
OBJS=Thread.cpp EventClass.cpp MutexClass.cpp main.cpp
EXECS = thread
all: $(EXECS)
thread: $(OBJS)
$(CC) $(CFLAGS) -o thread $(OBJS) $(LIBS)
clean:
rm -f *.o $(EXECS)
License
이 글과 연관된 모든 소스코드와 파일들은 The Code Project Open License (CPOL)을 따릅니다.
위 Walter Capers 씨가 원문 작성자이며, Code Project 홈페이지에서 만나보실(?) 수 있습니다.
다시한번 알려드리지만 이 글(한글로 번역된 이 포스팅)의 원문 작성자는 제가 아니며 Walter Capers 씨입니다. 원문의 라이센스가 CPOL을 따르기 때문에 번역본의 라이센스 역시 CPOL을 따르지만, 반드시 출처와 원문 저자, 역자 및 번역본 주소를 표기해 주시기 바랍니다.
이 글의 가장 아쉬운 점은 설명이 중복된 부분이 너무 많고, 나열하는 형태의 글이기 때문에 번역하면서도, 읽어보면서도 너무나 지루한 글이 되었습니다만, 이 글의 가장 좋았던 점은 보기 드문 멀티 플랫폼에 관한 글이었다는 것이었습니다. 일부 오역이 있을 수 있어욧! 그리고, 뭔지 말씀드리긴 곤란해서 이렇게만 말씀드리지만 제가 어떤 프로젝트에 참여하고 있어서 시간이 부족했... 핑계 죄송합니다... 하핳ㅋㅋ 조금 더 알차게 준비해서 내일 더 좋은 걸 가지고 나오겠습니다.
출처 표기하는 방법:
Creating a C++ Thread Class (Code Project, @Walter-Capers) / 역: Jay K (http://www.jayks.ml/6)
이런 형태로 출처와 원문 저자, 역자 및 번역본 주소가 잘 표기되어 있으면 좋겠습니다.
감사합니다.
'Translations' 카테고리의 다른 글
How to create an Operating System - Bare Bones, Moving Forward&FAQ (0) | 2017.07.13 |
---|---|
How to create an Operating System - Bare Bones (0) | 2017.07.12 |
A C++ Websocket Server for realtime interaction with Web client (0) | 2017.07.10 |
C++ Memory Leak Finder under Linux (0) | 2017.07.08 |