timeBeginPeriod(1) : 일단 시스템의 타이밍 해상도를 1ms로 바꿈=>시간 함수의 정밀도를 높이는 효과
를 해 주시면 Sleep()의 해상도를 높일 수 있습니다.
테스트 해보니 평균 15ms 걸리던 것이 해상도 높인 후에는 약 2ms 걸리네요. http://blog.naver.com/small3014?Redirect=Log&logNo=80175959872
[출처] Thread & Sleep|작성자 작은아이
프로세스들 중에서의 우선순위 설정
SetPriorityClass(GetCurrentProcess(),REALTIME_PRIORITY_CLASS);
프로내의 스레드 우선순위 설정
SetThreadPriority(hThread[0], THREAD_PRIORITY_ABOVE_NORMAL );
SetThreadPriority(hThread[1], THREAD_PRIORITY_HIGHEST);
SetThreadPriority(hThread[2], THREAD_PRIORITY_NORMAL);
SetThreadPriority(hThread[3], THREAD_PRIORITY_LOWEST);
http://blog.naver.com/ascbbs?Redirect=Log&logNo=40001109764
Windows는 실시간 OS가 아니기 때문에 근본적으로 실시간은 지켜질수 없다.
그러나 여러 방법을 사용하면 가능할 수도 있다.
방법으론 다음과 같이 크게 3가지가 있을수 있다.
1. RTC(RealTime Clock) 인터럽트를 이용하는 방법
2. 실시간을 위한 윈도우 써드파트를 이용하는 방법
3. 윈도우의 scheduling과 Process Class./Thread Priority를 이용하는 방법
1번은 DDK로 IRQ8번을 후킹하여 RTC가 갖고있는 Period Interrupt기능을 이용할 수 있다.
이를 위해서는 디바이스 드라이버 프로그래밍을 할 줄 알아야 하는 번거로움이 있고
IDT(INterrupt Descriptor Table)을 직접 조작할수 있는 기술이나 다른 사람이 짠 프로그램을
빼끼는 능력이 있어야 하지만 내가 해본바로는 하드웨어 인터럽트기 때문에 hard Realtime을
구현할 수 있다. 이에 대한 내용은 이미 내 블로그에 찾아보면 있을 것이다.
대신에 RTOS처럼 여러 여러 쓰레드를 사용할수 없고 2의 배수로 나눈 시간으로 밖에
주기를 설정할 수 없는 단점이 있다. 나는 512Hz로 현재 모션 컨트롤러를 제어 하고 있다.
둘째로 돈이 가장 많이 드는 방법으로 Third Part를 사서 사용하는 방법이다.
이것들은 내가 본것만도 몇가지 되지만 그중 가장 괜찮다고 하는게 INTime이라는 것이다.
이것이 설치되면 Window커널위에 INTime RT 커널이 위치해서 윈도를 완전히 장악 하고
있는것 같다. Realtime의 구현은 일반 Application에서 되는것이 아니고 따로 RT module을
작성해야하며 Application과 다양한 방법의 통신으로 운영되는 것 같다.
셋째로 윈도우 자체 기능을 최대한 활용하는것으로 Hard RealTime은 될수 없지만
어느정도는 잘되는듯 하다.
일단 시스템의 타이밍 해상도를 1ms로 바꾸기 위해 timeBeginPeriod(1)을 사용하다.
이렇게 되면 스케쥴링을 위한 최소 타임이 Win2K인경우 10ms에서 1ms으로 변경된다.
그리고 프로그램의 첨 시작에
SetPriorityClass(GetCurrentProcess(),REALTIME_PRIORITY_CLASS);
을 삽입하여 현 프로그램에 대한 프로세서 우선순위를 최고로 올려놓는다.
다음으로 실시간을 구현하고자 하는 쓰레드를 가장 높은 우선순위로 다음과 같이 생성한다.
AfxBeginThread(MyThread,this,THREAD_PRIORITY_TIME_CRITICAL);
이렇게 되면 일단 모든 준비는 완료된 것이다.
다음으로 쓰레드에서 반드시 해야할 일은 Sleep을 넣는것인데 이것을 넣지 않으면
윈도우는 완전히 멈춰버리고 오직 이쓰레드만 동작하게 된다.
Sleep은 다음과 같은 방법으로 넣는다. 예를들어 100Hz로 정확하게 구동하는 쓰레드를
만들고 싶다면 다음과 같은 구조로 짠다.
#define HZ 100.0
double pretime, ctime;
pretime = GetPrecisionTime();
while(flag)
{
ctime = GetPrecisionTime();
if (ctime-pretime >= 1.0/Hz)
{
pretime = ctime;
// 여기에 원하는 코드를 넣는다,
Sleep(7); // <<------- 매우 중요한 부분이다.
}
}
위에서 사용된 GetPrecisionTime은 timeGetTime이나 GetTickCount가 1ms단위로 리턴하는것에
비해 nanosec단위로 리턴하기때문에 아주 정확하다. 함수의 원형은 아래와 같다.
double GetPrecisionTime(void)
{
LARGE_INTEGER lpFrequency;
LARGE_INTEGER lpPerformanceCount;
QueryPerformanceFrequency(&lpFrequency);
QueryPerformanceCounter(&lpPerformanceCount);
return (double)lpPerformanceCount.QuadPart /(double)lpFrequency.QuadPart;
}
여기서 의문이 왜 Sleep에 7이라고 했으며 왜 내 코드를 수행하는 부분 안에 넣었을까이다.
자 그럼 의문을 하나하나 풀어가보자.
timeBeginPeriod로 1ms 설정했기 때문에 Sleep의 최소단위는 1ms이다. 우리는 반드시
Sleep(1)이상을 넣어야 하는 의무가 있는 조건이다. 그럼 매번 Sleep(1)을 호출한다면
최소 1ms는 안지켜질 확률이 매우 커진다. 우린 1ms이상의 시간을 지켜야 하는 사명이 있다.
그렇다고 Sleep을 안쓰게 되면 윈도는 멈춰버린다. 해결책은 어짜피 우린 100Hz (10ms)로만
동작하면 되고 또 우리의 코드가 그리많은 시간이 필요로 하지 않는다. 아하~.. 그렇다면
우리 의 코드가 수행된다음 다음 시간까진 아직 많은 시간이 남아 있기 때문에
그때 Sleep을 주면 되겠다는 생각이 떠오른다. 그래서 시간을 측정하고 내 코드가 수행되는
그 블록안에 Sleep이 위치 해 있는것이다.
SLeep(7)은 왜했을까 ?. Sleep(1)을 하는게 생각으론 좋겠지만 그렇게 된다면 이 쓰레드 이외의
다른 작업에 겨우 1ms의 시간밖에 할당을 안해주기 때문에 전체적으로 윈도우가 느려짐을 느낄
수 있다. 그렇다고 Sleep(10)을 주면 100Hz를 달성 할 수 없다. 이것저것해보니 7~8이 가장
적당한 듯 하다.
대충 의문이 풀렸는가?.. 그냥 쉽게 말해 윈도우는 평소에 아무짓도 안하고 시간만 보고 있고
시간이 되면 내작업하고 7ms동안만 윈도우의 하던 작업을 하게 시간을 내주는것이다.
이것이 Idea는 단순하지만 하드디스크를 억세스 하지 않는 조건이라면 아주 잘 지켜진다.
단 시험해본바로 98/2K에서만 잘되는것 같다. XP는 내부적으로 먼가 틀린가 보다.
똑같은 코드로 돌려봐도 잘 안지켜 지는것 같다.
아무튼..귀찮은 실시간을 조금이나마 구현하고자 하는데 도움이 됬으면 좋겠다.
[출처] Window에서 최대한 Realtime을 지켜보자.|작성자 캐럴리
http://blog.naver.com/sealriel/10145666873
[MFC 응용프로그램] 13장 멀티스레드 MFC응용프로그램
멀티스레드 MFC 응용프로그램 만들기!
Multi-thread programming
윈도우 프로그래밍의 특징
- GUI(창을 통해 처리한다)
- 멀티 태스킹이 전제된다.(메시지 드리븐(구동) 구조이다) 그 이전에는 프로그램 드리븐 구조였다.
- 메시지 드리븐이 개발하게 된게 멀티스레딩 개념 때문에 나왔다.
01멀티스레드 기초
- 멀티태스킹과 멀티스레딩
- 멀티태스킹
- 운영체제가 여러 개의 프로세스를 동시에 실행한다.(운영체제 개발자가 신경 써야할 부분이다.)
- 멀티스레딩
- 응용 프로그램이 여러 개의 스레드를 동시에 실행한다.(응용프로그램 개발자가 신경써야 할 부분이다.)
- 하나의 프로세스 안에 여러 개의 실행흐름(스레드)를 가진다.
- 프로세스와 스레드.
프로세스
- 실행 중인 프로그램(도스와 윈도우 프로세스 개념이 다르다.)
- 도스 유닉스의 프로세스 개념은 지금의 스레드와 비슷하다.
- 실행흐름이 아니고 정적인 개념으로 구성요소들 환경들의 집합을 프로세스라고 한다.
- 어떤 스레드들의 공간을 제공해주는 개념이다.
프로세스 구성 요소
- 가상 주소 공간 : 32비트 윈도우의 경우 4GB(사용자 영역 2GB+커널 영역 2GB)
- 실행 파일과 DLL : 코드, 리소스, 데이터(전역 변수, 정적 변수)
- 힙 / 환경 변수 / 하나 이상의 스레드 : 프로세스 커널 객체
- 운영체제가 프로세스를 위해 할당한 각종 자원 : 파일, 소켓, ...
- 프로세스 구성 요소 (cont'd)
- 스레드
- 프로세스의 가상 주소 공간에 존재하는 하나의 실행 흐름
- 운영체제는 각 스레드에 CPU 시간을 나누어 할당함으로써 여러 개의 스레드가 동시에 실행되는 효과를 냄
- 윈도우가 등장하면서 나온 개념이다.
- 동적으로 시간의 흐름에 따라 바뀌는 것이다.
- 스레드 : 실행의 단위 Process->프로그램이 프로세스가 된다.
- 실행의 단위(최소)가 스레드다.
- main(){ } 종료되면 프로세스가 종료되는 것이다.
- CPU 연산 (코어가 하나있다고 가정하면) CPU 스케쥴링 (실행의 최소단위가 Thread가 된다)
- Process <- 자원이 할당된다
- Thread – 프로세스에 할당된 자원을 공유한다.
각각의 스레드별로 각각의 스택 객체가 생성되고 그 안에서 지역변수가 관리된다.
- 스레드 구성 요소
- 스택
- 함수 인자 전달과 지역 변수 저장을 위한 공간
- 스레드 지역 저장소(TLS, Thread Local Storage)
- 스레드별 고유 데이터를 저장하기 위한 공간
- 스레드 커널 객체
프로세스와 스레드 구성요소
- CPU 스케줄링
운영체제가 한정된 CPU 시간을 여러 개의 프로세스(전통적인 유닉스 운영체제) 혹은 스레드(윈도우 운영체제)에 분배하는 정책
- 윈도우의 CPU 스케줄링
- 우선순위(Priority)에 기반한 CPU 스케줄링 기법을 사용
- 우선순위가 높은 스레드에 CPU 시간을 우선 할당
- 스레드의 우선순위 결정 요소
- 우선순위 클래스(Priority Class)
- 우선순위 레벨(Priority Level)
- 우선순위 클래스
- 프로세스 속성(프로세스한테 매겨지는 우선순위 클래스)
- 같은 프로세스가 생성한 스레드는 모두 동일한 우선순위 클래스를 가짐
- 이것이 우선 순위 클래스
- 우선순위 클래스 종류
- REALTIME_PRIORITY_CLASS(실시간)
- HIGH_PRIORITY_CLASS(높음)
- ABOVE_NORMAL_PRIORITY_CLASS(보통 초과; 윈도우2000/XP 이상)
- NORMAL_PRIORITY_CLASS(보통)
- BELOW_NORMAL_PRIORITY_CLASS(보통 미만; 윈도우2000/XP 이상)
- IDLE_PRIORITY_CLASS(낮음)
- 우선순위 레벨
- 스레드 속성
- 같은 프로세스에 속한 스레드 간 상대적인 우선순위를 결정
- 우선순위 레벨 종류
- THREAD_PRIORITY_TIME_CRITICAL
- THREAD_PRIORITY_HIGHEST
- THREAD_PRIORITY_ABOVE_NORMAL
- THREAD_PRIORITY_NORMAL
- THREAD_PRIORITY_BELOW_NORMAL
- THREAD_PRIORITY_LOWEST
- THREAD_PRIORITY_IDLE
- 우선순위 클래스 + 우선순위 레벨
ð 스레드의 기본 우선순위(Base Priority)
- 스레드 동기화 (스케줄링보다 좀더 중요함)
- 병렬 처리에서 동기화라는 것은 서로 다른 스레드 간의 실행 순서를 보장하기 위해서 구속 조건을 강제로 거는 것이다. 동시에 여러개의 쓰레드가 동일한 자료를 접근하여 조작하고 그 실행결과를 접근하는 특정 순서에 의존하는 상황을 경쟁상황이라고 부른다
즉, 동기화란 스레드의 실행순서를 구성하고 공유하는 데이터를 관리 하는것
02 MFC 스레드
- MFC 스레드 종류
- 작업자 스레드(Worker Thread)
- UI 스레드가 없는 스레드
- 메시지 루프가 없다.
- 화면에 보이지 않는 백그라운드 작업을 수행할 때 적합
- 입출력을 안하면서 돌아가는 스레드.(입출력을 안하니까 메시지 루프가 필요 없다.)
사용자가 입력할 때 백그라운드에서 돌아간다.
- 사용자 인터페이스 스레드(User Interface Thread)
- 두개가 동시에 돌아가고 있다고 생각하면 되는게 UI Thread
- 오히려 이 스레드는 잘 안쓰임.
- 별도의 메시지 루프가 있다.
- 윈도우를 만들고 출력을 하거나 사용자의 입력을 받는 등의 작업을 별도의 스레드로 처리할 때 적합
- 작업자 스레드
AfxBeginThread : CWinThread 타입의 스레드 객체(Thread Object)를 동적으로 생성하고 내부적으로 스레드를 만든 후 스레드 객체의 주소값을 리턴
CWinThread* AfxBeginThread(
AFX_THREADPROC pfnThreadProc,
LPVOID pParam,
int nPriority = THREAD_PRIORITY_NORMAL,
UINT nStackSize = 0,
DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
);
- pfnThreadProc: 스레드 실행 시작점이 되는 함수(=제어 함수)의 주소
- 제어 함수 형태 ð UINT 함수명(LPVOID pParam);
- pParam: 제어 함수 실행 시 전달할 인자(32비트 포인터형)
- nPriority: 스레드 우선순위 레벨(이 이하는 디폴트 매개변수가 있어 안 써도 된다.)
- nStackSize: 스레드 스택 크기
- dwCreateFlags: 스레드 생성을 제어하는 옵션
- 0 또는 CREATE_SUSPENDED
- lpSecurityAttrs: 보안 설명자와 핸들 상속 정보
- CWinThread 클래스의 유용한 함수들
int CWinThread::GetThreadPriority(); - 스레드 우선순위 레벨 값을 얻음
BOOL CWinThread::SetThreadPriority(int nPriority); - 스레드 우선순위 레벨 값을 변경
DWORD CWinThread::SuspendThread(); - 스레드 실행을 일시 중지
DWORD CWinThread::ResumeThread(); - 일시 중지된 스레드의 실행을 재개
- 작업자 스레드 종료
- 방법 1: 스레드 제어 함수가 종료 코드를 리턴
- 0을 리턴하면 일반적으로 정상 종료를 뜻함
- 방법 2: 스레드 제어 함수 내에서 AfxEndThread() 함수를 호출(스레드 객체를 이용하는 방법)
void AFXAPI AfxEndThread(UINT nExitCode, BOOL bDelete=TRUE);
- nExitCode: 스레드 종료 코드
- bDelete: 스레드 객체를 제거할 것인지를 나타냄
- FALSE를 사용하면 스레드 객체 재사용 가능
- UI 스레드
UI 스레드는 작업자 스레드와 달리 메시지 루프가 있어서 사용자 입력이나 기타 메시지 형태로 전달되는 이벤트를 처리할 수 있다. 대표예는 응용 프로그램 객체이다.
UI 스레드 생성 절차
- CWinThread 클래스를 상속받아 새로운 클래스를 생성
- 클래스 선언부와 구현부에 각각 DECLARE_DYNCREATE, IMPLEMENT_DYNCREATE 매크로를 선언
- CWinThread 클래스가 제공하는 가상 함수 중 일부를 재정의
- CWinThread::InitInstance() 함수는 반드시 재정의. 나머지 함수는 필요에 따라 재정의
- AfxBeginThread() 함수로 새로운 UI 스레드 생성
- UI 스레드 생성
CWinThread* AfxBeginThread(
CRuntimeClass* pThreadClass,
int nPriority = THREAD_PRIORITY_NORMAL,
UINT nStackSize = 0,
DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
);
- CWinThread 타입의 스레드 객체(Thread Object)를 동적으로 생성하고 내부적으로 스레드를 만든 후 스레드 객체의 주소값을 리턴
- pThreadClass: 클래스 정보를 담고 있는 CRuntimeClass 구조체
- 인자 전달 형태 ð RUNTIME_CLASS(클래스이름)
- nPriority: 스레드 우선순위 레벨
- nStackSize: 스레드 스택 크기
- dwCreateFlags: 스레드 생성을 제어하는 옵션
- 0 또는 CREATE_SUSPENDED
- lpSecurityAttrs: 보안 설명자와 핸들 상속 정보
- UI 스레드 종료
- 방법 1: WM_QUIT 메시지를 받아서 메시지 루프가 종료
- 방법 2: 스레드 제어 함수 내에서 AfxEndThread() 함수를 호출
03 스레드 동기화
- MFC 클래스 계층도(그런게 있다 정도만 알면 됨)
- 클래스 요약
- CSyncObject
- 스레드 동기화 클래스를 위한 공통의 인터페이스 제공
- 순수가상함수를 포함하므로 직접 쓰진 않는다.
- CCriticalSection, CEvent, CMutex, CSemaphore
- 윈도우 운영체제에서 제공하는 스레드 동기화 객체(임계 영역, 이벤트, 뮤텍스, 세마포어)를 편리하고 일관되게 사용할 수 있게 해줌
- 스레드 동기화 개념(꼭 알아야 함!)
스레드 동기화가 필요한 상황
- 둘 이상의 스레드가 공유 자원에 접근하는 경우
- 한 스레드가 작업을 완료한 후, 기다리고 있는 다른 스레드에 알려주는 경우
- 스레드 동기화 원리
- 임계영역
- 용도
- 공유 자원에 접근하는 다수의 스레드가 있을 때 오직 하나의 스레드만 접근할 수 있게 함
- 장점
- 속도가 빠름
- 단점
- 서로 다른 프로세스에 속한 스레드 간 동기화에는 원칙적으로 사용할 수 없음
사용예 : 두 스레드 중 Lock() 함수를 먼저 호출하는 쪽이 공유자원에 접근할 수 있고, 나중에 호출하는 쪽은 상대편이 Unlock()함수를 호출할 때까지 대기한다.
- 뮤텍스(앞 과 똑 같은 개념)
- 용도
- 공유 자원에 접근하는 다수의 스레드가 있을 때 오직 하나의 스레드만 접근할 수 있게 함
- 임계 영역과 기능이 동일
- 장점
- 서로 다른 프로세스에 속한 스레드 간 동기화에 사용할 수 있음
- 단점
- 임계 영역보다 속도가 느림
임계 영역과 달리 커널 모드로 동작한다.
- 뮤텍스 생성
CMutex::CMutex(
BOOL bInitiallyOwn = FALSE,
LPCTSTR lpszName = NULL,
LPSECURITY_ATTRIBUTES lpsaAttribute = NULL
);
- bInitiallyOwn: TRUE면 뮤텍스를 생성한 스레드가 소유자가 됨
- lpszName: 뮤텍스에 이름을 부여함. NULL을 사용하면 이름 없는(Anonymous) 뮤텍스가 됨
- lpsaAttribute: 보안 설명자와 핸들 상속 정보
- 이벤트
- 이벤트
- 신호(Signaled)와 비신호(Nonsignaled) 두 개의 상태를 가진 동기화 객체
- 용도
- 한 스레드가 작업을 완료한 후, 기다리고 있는 다른 스레드에 알려줄 때 주로 사용
- 이벤트 사용 절차
- 이벤트를 비신호 상태로 생성
- 한 스레드가 작업을 진행하고, 나머지 스레드는 이벤트에 대해 Lock() 함수를 호출함으로써 이벤트가 신호 상태가 될 때까지 대기함(Sleep)
- 스레드가 작업을 완료한 후 이벤트를 신호 상태로 바꿈
- 기다리고 있던 스레드 중 하나 혹은 전부가 깨어남(Wakeup)
- 종류
- 자동 리셋(Auto Reset)
- 이벤트를 신호 상태로 바꾸면, 기다리는 스레드 중 하나만 깨운 후 자동으로 비신호 상태가 됨
- 수동 리셋(Manual Reset)
- 이벤트를 신호 상태로 바꾸면, 기다리는 스레드 전부를 깨운 후 계속 신호 상태를 유지함
- 비신호 상태로 바꾸려면 명시적으로 함수를 호출해야 함
- 이벤트 생성
CEvent::CEvent(
BOOL bInitiallyOwn = FALSE,
BOOL bManualReset = FALSE,
LPCTSTR lpszName = NULL,
LPSECURITY_ATTRIBUTES lpsaAttribute = NULL
);
- bInitiallyOwn: FALSE면 비신호, TRUE면 신호 상태
- bManualReset: FALSE면 자동 리셋, TRUE면 수동 리셋
- lpszName: 이벤트에 이름을 부여함. NULL을 사용하면 이름 없는(Anonymous) 이벤트가 됨
- lpsaAttribute: 보안 설명자와 핸들 상속 정보
- 이벤트 상태 변경
BOOL CEvent::SetEvent(); - 이벤트를 신호 상태로 바꿈
BOOL CEvent::ResetEvent(); - 이벤트를 비신호 상태로 바꿈
- 세마포어
- 자원에 접근할 수 있는 스레드 수를 제어하는 동기화 객체
- 가용 자원의 개수를 나타내는 리소스 카운트(Resource Count)를 유지함으로써 동시에 실행될 수 있는 스레드 수를 조절 가능
- 세마포어 사용 절차
- 세마포어를 생성. 이때 리소스 카운트를 가용 자원의 개수로 초기화
- 자원을 사용할 스레드는 자신이 필요한 자원의 개수만큼 Lock() 함수를 호출하는데, Lock() 함수가 성공할 때마다 리소스 카운트 값은 1씩 감소.
- 리소스 카운트가 0인 상태에서 Lock() 함수를 호출하면 해당 스레드는 대기함(Sleep)
- 자원 사용을 마친 스레드는 자신이 사용한 자원의 개수만큼 Unlock() 함수를 호출하는데, Unlock() 함수가 성공할 때마다 리소스 카운트 값은 1씩 증가
- 대기중인 다른 스레드가 있다면 깨어남(Wakeup)
- 세마포어 생성
CSemaphore::CSemaphore(
LONG lInitialCount = 1,
LONG lMaxCount = 1,
LPCTSTR pstrName = NULL,
LPSECURITY_ATTRIBUTES lpsaAttributes = NULL
);
- lInitialCount: 리소스 카운트의 초기값
- lMaxCount: 리소스 카운트의 최대값
- pstrName: 세마포어에 이름을 부여함. NULL을 사용하면 이름 없는(Anonymous) 세마포어가 됨
- lpsaAttribute: 보안 설명자와 핸들 상속 정보
[출처] [MFC 응용프로그램] 13장 멀티스레드 MFC응용프로그램|작성자 럭셔리프로그래머
'운영체제 & 병렬처리 > Multithread' 카테고리의 다른 글
Reentrant 와 Thread-safe 의 차이 (0) | 2015.12.16 |
---|---|
error C2719: 'size': __declspec(align('16'))를 사용하는 정식 매개 변수는 정렬되지 않습니다. (0) | 2013.05.11 |
MFC 스레드 돌릴때 SetWindowText 에서 문제가 발생한다 (0) | 2012.12.28 |
EnterCriticalSection 안에서 쓰레드가 죽는다면? (0) | 2012.12.27 |
멀티쓰래드일때 멤버함수또한 동기화를 해줘야한다 (0) | 2012.12.26 |