10.메모리와 스레드

본 장 메모리관리에 대한 내용과 스레드, 그리고 스레드의  동기화에 대한 부분에 대한 내용입니다. 윈도우 95 이후부터는 우리가 사용하는 메모리의 용량이 매우 크게 증가하였기 때문에 메모리 관리에는 그렇게 큰 문제가 없습니다.  SDK에세 제공하는 다양한 함수를 이용하면 자원이 허용하는한 최대의 메모리를 이용할수 있기 때문입니다. 본 장에서는 메모리를 할당하는 방법에 대한 내용을 첫항목으로 설명합니다. 윈도우에서는 여러 통신 방법이 있습니다. 그중에서 메모리 맵핑파일을 이용하여 서로 데이터를 교환 하는 방법을 본장에서 소개합니다.

스레드란 하나의 독립형태로 구동되는 모듈입니다. 많은 시간을 요하는 작업을 수행하거나 독립적으로 계속적으로 작업이 수행하고자 할경우에는 스레드를 이용합니다. 본장에서는 스레드를 이용한 기본방법과 여러 스레드를 동기화 시키는 방법으로,뮤텍스,세마포,크리티컬 섹션에 대해서 설명합니다.

Chapter 등급: 초급,중급,고급

Chpater 구분: 필수

공부방법:

전체적으로 어떤 내용인가를 독서하듯이 읽어 주시면 됩니다.  메모리 관리에서 대부분이 new연산을 사용하고 특수한 경우에만 SDK메모리 함수를 이용하기 때문입니다.

스레드 부분에서는 2 항목인 스레드만 이해하시면 됩니다. 후에 고수준의 데이터베이스 엔진을 설계하거나 데이터 보안이 필요한 부분에서 다중 스레드를 사용할때는 3항목을 이용할수도 있을것입니다. 그러나 일반 어플리케이션 프로그램에서는 많이 사용하지 않는 항목이므로 그냥 읽어 보시면 됩니다.

1.메모리 관리

윈도 98는 32비트 OS입니다. 기존의 16비트 OS와는 달리 그 속도뿐만 아니라 처리 속도도 매우 빠르게 구성되어 있습니다. 윈도 98에서는 총 메모리를 4GB(기가바이트)까지 사용 가능합니다. 굉장한 매모리 매핑 기능을 가지고 있는 것이죠. 실질적으로 개인용 PC는 보통 8∼16메가를 가지고 있고 좀더 많이 사용한다면 32메가 정도인데 4기가바이트의 메모리를 컨트롤할 수 있다고 볼 때 메인메모리가 너무 적죠?

그래서 윈도 98는 가상(virtual) 메모리를 사용합니다. 가상 메모리란 메인메모리와 함께 보조 기억 장치, 보통 하드(HDD)라고 하는 곳의 일부분을 메모리로 사용하는 것입니다. 이 가상 메모리는 모두 번지가 지정되어 있고 이 번지에 프로그램 데이터가 적재되는데 이 많은 메모리 번지를 직접 프로그래머가 설정한다면 얼마나 힘들겠습니까? 하지만 WIN 98의 커널(KERNEL)이 모든 메모리를 자동적으로 매핑하고 전달해

주기 때문에 사용자나 프로그램 제작자는 메모리에 상관없이 프로그램을 사용하거나 제작하면 됩니다. 사용자가 특정 프로그램을 실행시킬 때 그 프로그램실행시 요구되는 메모리 용량을 커널에게 알려 주고 커널은 그에 해당하는 메모리를 설정합니다. 메인메모리가 모자란다면 보조 기억 장치까지 동원하여 프로그램이 적절하게 구동할 수 있게 영역을 만들어 주게됩니다.

<1>가상메모리 (Virtual memory)

16메가급의 펜티엄 컴퓨터에 윈도를 깔았습니다. 기본 메모리가 16메가, 하드는 1.2기가 그럼 윈도 98는 총 얼마나 많은 메모리를 사용할 수 있을까요? 현재 남아 있는 디스크 용량／메인메모리 16메가라고 볼 수 있습니다. 즉 100메가가 남아 있다면 116메가의 메모리를 사용할 수 있다는 이야기이죠. 윈도 98는 가상 메모리 시스템을 사용하기 때문에 디스크 자체를 메모리로 간주하고 사용합니다.

그런데 이런 메모리도 어떤 구조로 저장되어야 하잖아요. 윈도 98에서는 메모리를 페이지 단위로 저장합니다. 커널은 페이지를 메모리에서 디스크로 디스크에서 메모리로 이리저리 움직여 줍니다. 즉, 윈도 98에서는 어떤 데이터가 일정한 장소에 고정되어 있는 것이 아니고 필요에 따라 이리저리 움직인다는 것이죠. 그럼 왜 이렇게 움직여야 하는가? 어떤 프로그램을 실행하려고 할때 새로운 메모리가 필요하다고 합니다. 그런데 현재 페이지는 1, 2, 3, 4, 5 다섯 개가 만들어져 있는데 현재 사용하는 메모리가 2번이라고 하고 새로 만들 메모리를 6번으로 하면 2번 갔다가 다시 6번 갔다가 정신이 없죠. 따라서, 6번 메모리를 2번 뒤에 매핑하면 프로그램 실행시 빠르게 실행할 수 있습니다. 1, 2, 6, 3, 4, 5가 되겠죠?가상 메모리 시스템은 언제나 페이지를 보유하고 있습니다. 페이지를 보유하는 이유는 실제 메모리에 있는 데이터와 보조 기억장치에 있는 데이터간의 원할한 연결을 위해서 이지요. 윈도95의 가상 메모리 시스템은 일반적 전산이론의 가상 메모리 약간 다른 형태를 가지고 있습니다.

일반적 전산이론에서는 가상메모리와 실제메모리간에 색인 테이블을 페이지라는 메모리에 설정을 하고 페이지를 통해서 메모리를 검색하게 하고 또한 모든 프로그램에게 똑같은 메모리를 할당하는 개념과는 약간 다른 개념을 사용하고 있습니다. 이런 이유는 간단하지요 16비트 윈도프로그램과 도스 영역 프로그램을 함께 사용할려면 메모리 관리가 특수하게 설정해야 하기 때문입니다..

큰메모리를 필요로 할 경우 이럴 때 사용하는 함수가 VirtualAlloc함수입니다. VirtualAlloc 함수를 이용하면 시스템에서는 필요한 메모리를 할당합니다. 이렇게 할당한 메모리를 어떤 특정 변수에 걸어줄 경우 이때 사용하는 함수가 VirtualLock입니다.

이 때 현재 사용 프로세스는 메모리를 마음껏 사용합니다. 사용이 끝나면 현재 메모리를 다른 프로세스가 사용할 수 있도록 해주어야겠죠. 만일 예약을 해제하지 않으면 그 메모리는 영원히 아무도 사용 못할 수도 있고 그렇게 되면 치명적인 버그로서 프로그램이 멎을 수도 있으니까요. 이 때 예약을 해제하는 함수가 VirtualUnLock 함수입니다. 더 이상 이 페이지를 사용하지 않는다면 지워야 하겠죠. 이 때 사용하는 함수가 VurtualFree입니다. 지금까지 설명한 가상메모리 사용방법은 그림 1-10-1과 같습니다.

(그림 파일)

(그림 1-10-1) 가상 메모리 사용방법

<2>가상메모리 함수

가상 메모리를 이용하는 예는 그림 1-10-2와 같습니다.

(그림파일)

(그림 1-10-2)가상메모리 이용 예

1,024바이트의 메모리를 사용하고자 한다면 VirtualAlloc함수를 이용하여 그림 1-10-2의 ①과 같이 하면 됩니다.

①항목에서 사용한 VirtualAlloc 함수의 형태는 다음과 같습니다.

LPVOID VirtualAlloc(

LPVOID lpAddress, // 예약하거나 위탁받고 싶은 주소

DWORD dwSize, // 메모리 크기

DWORD flAllocationType, // 메모리 Allocation 형태

DWORD flProtect // 보호 모드 설정 };

lpAddress는 예약하고 싶은 주소를 써 줍는 인자입니다.. 현재 어떤 데이터가 들어 있는 페이지 주소가 있다면 그 주소를 써 주면 됩니다. NULL을 하면 커널이 자동적으로 새로운 주소를 설정해 줍니다. dwSize는 메모리의 크기이고 flAllocationType는 메모리 할당 형태를 말하는 것인데 3가지 종류가 있습니

다.

MEM_COMMIT : 디스크가 메모리에 메모리를 만들어 위탁한다는 말입니다. 만일 현재 위탁하려는 페이지가

이미 위탁을 받았다면 그때는 사용을 금지합니다.

MEM_RESERVE : 메모리를 예약한다는 의미입니다. 기존에 사용하던 페이지를 그대로 예약한다는 것이죠.

새로 만들지 않고요.

MEM_TOP_DOWN : 가장 최근에 맞게 메모리를 만듭니다.

flProtect란 보호 모드 설정인데 8개의 모드가 있습니다. 그 중 중요한 3개의 모드를 설명하겠습니다.

PAGE_READONLY : 읽기 전용으로

PAGE_READWRITE : 읽기 쓰기 겸용으로

PAGE_NOACCESS : 다른 곳에서 사용을 할 수 없도록 접근 불가입니다.

그림 1-10-2에서 ①과 같이 메모리를 할당한다음 이 메모리를 어떤 변수에 위탁해 줍니다. 이렇게 하는 내용이 ②항목과 같습니다. ②항목이 실행되면 lpAddress 에 1024크기의 메모리가 설정된것입니다. 이렇게 설정한후에 lpAddress를 사용하시면 됩니다. 이메모리 사용이 끝난후에는 메모리를 해제하여야 합니다. 메모리를 해제하는 부분이 그림 1-10-2의 ④와 같습니다. 마지막으로 설정한 위탁받은 메모리를 지우면 가상메모리를 사용하는 내뇽이 종료 됩니다. 그림 1-10-2의 ⑤항목이 메모리를 지우는 내용입니다.

다음은 그림 1-10-2에서 사용한 가상메모리 함수의 형태에 대한 설명입니다.

BOOL VirtualLock(

LPVOID lpAddress, //위탁을 받는 포인터

DWORD dwSize // 위탁을 받는 메모리 크기

);

VirtualLock 함수는 VirtualAlloc에 의하여 할당된 메모리를 사용할 수 있도록 위탁을 받겠다는 의미입니다.

lpAddress는 VirtualAlloc에 의하여 할당된 메모리 포인터를 말하고 dwSize는 위탁받겠다는 메모리 크기를 써 줍니다. 성공하면 TRUE, 실패하면 FALSE를 리턴합니다.

BOOL VirtualUnlock(

LPVOID lpAddress, // 예약을 해제하는 포인터

DWORD dwSize // 위탁을 해제하는 메모리 크기

);

VirtualUnlock는 위탁을 해제한다는 말입니다. 인자는 VirtualLock와 같습니다.

BOOL VirtualFree(

LPVOID lpAddress, // 메모리 주소 포인터

DWORD dwSize, // 메모리 크기

DWORD dwFreeType // 해제하는 형태

);

설정한 메모리를 해제합니다. lpAddress와 dwSize는 위의 설명을 참조하세요.

dwFreeType은 해제하는 형태인데 단지 예약만 취소할 경우와 완전히 제거하는 경우 두 가지로 나눌 수 있습니

다. 예약만 취소하는 형태는 MEM_DECOMMIT입니다. "아차 잘못했다. 메모리 복구해야지" 이럴 때 다시 예약을

하면 복구됩니다. MEM_RELEASE는 완전히 메모리를 삭제하는 것입니다

<2>GlobalAlloc과 LocalAlloc

GloablAlloc와 LoaclAlloc는 일반적으로 메모리르 할당할때 사용하는 SDK용 메모리 관리 함수입니다.

GlobalAlloc은 전역, 즉 윈도가 사용하는 Global Heap 영역을 사용하는 것이고 LoaclAlloc은 Local Heap를 사용하는 것인데 윈도 95에서는 이것에 대한 차이가 없습니다. GlobalAlloc하든 LoaclAlloc하든 그냥 가상 메모리 형태로 사용합니다. 따라서, Global과 Local이 필요없지요. 다만 GlobalAlloc과 LocalAlloc은 Heap 영역에 메모리가 할당됩니다. VirtualAlloc과 GlobalAlloc, LocalAlloc의 차이점은 VirtualAlloc은 64비트 메모리 주소를 리턴하는 반면 GlobalAlloc과 LocalAlloc은 32비트 메모리 주소를 리턴한다는 것입니다. 62비트 메모리 주소를 준다는 것은 그만큼 많은 메모리를 사용할수 있다는 것이고 GlobalAlloc나 LocalAlloc는 그만큼 작은 메모리를 사용할수 있다는 것입니다.GlobalAlloc과 LocalAlloc은 메모리를 설정한 다음 이 메모리를 다른 어떤 포인터가 Lock해야 합니다. 즉 메모리가 설정되어도 이 메모리 자체가 움직이고 있기 때문에 주소가 바뀌게 되죠. 이런 메모리를 또 하나의 어떤 포인터가 잡아 주어야 한다는 것이죠. 다음은 1024 크기의 메모리를 할당하고 사용하는 예입니다.

LPSTR lpMem; // 만들 메모리

LPSTR lpHandle; //메모리를 핸들링하는 변수

lpMem=GlobalAlloc(GMEM_MOVEABLE,1024);// 메모리를 설정함 메모리가 움직일

//수 있도록 함.

lpHandle=GlobalLock((LPSTR)lpMem); //메모리를 예약함.

: 데이터 사용

GlobalUnLock(lpMem); //메모리의 예약을 해제함.

GlobalFree(lpMem); //메모리의 메모리를 지움.

위의 예에서 사용한  Gloabl 계열의 메모리 함수는 다음과 같습니다.

GLOBAL GlobalAlloc(

UINT uFlags, // 메모리에 적재 속성

DWORD dwBytes // 적재할 메모리 크기

);

GlobalAlloc 함수는 메모리를 새로 만드는 함수입니다. 이곳의 메모리 적재 속성에는 여러 종류가 있는데 중

요한 것은 GEM_MOVEABLE, 즉 이동이 가능하게 메모리를 만든다는 말입니다.

GEM_DDESHARE라는 속성은 메모리를 두 개의 프로그램에서 공유하자는 속성인데 윈도 3.1이전 버전에서는 실질적으로 지원이 되지 않았으나 윈도 95 이후에서는 완벽하게 지원해 줍니다.

LPVOID GlobalLock(

HGLOBAL hMem // 메모리 번지

);

현재 설정된 메모리를 Lock이라 합니다. Lock한다는 말은 현재 메모리를 움직이지 못하게 하고 내가 사용할 때 다른 곳에서 접근 및 수정 등을 할 수 없게 핸들을 내가 갖겠다는 이야기입니다.

BOOL GlobalUnlock(

HGLOBAL hMem // 메모리 번지

);

lock했던 메모리를 해제합니다.

HGLOBAL GlobalFree(

HGLOBAL hMem // 메모리 번지

);

현재 메모리를 지웁니다.

<3>Heap에 메모리 사용 함수

디스크 장치가 이동이되지 않고  메인메모리에 데이터를 적재할 경우 GlobalAlloc 함수를 이용여 GMEM_FIXED 이나,GPTR 인자를 사용하면 됩니다.

디스크에 매핑된 메모리를 사용할 때의 단점은 데이터를 읽고 사용하기가 heap 영역에서 접근하는 것보다 속도가 느리다는 것입니다. 애니메이션이나 동영상을 사용하는 프로그램이 있다면 먼저 디스크 영역에서 데이터를 올려서 빠른 속도로 처리하여 화면에 출력해 주어야 합니다. 이럴 때는 꼭 Heap 영역에 데이터를 적재해야겠지요. 여기에 HeapCreat 함수를 사용하여 메모리에 만들어 주고 HeapAlloc라고 하여 메모리를 설정합니다. HeapCreate로 본 프로그램에서 사용할 Heap 영역을 미리 만들어 주고 만들어진 영역에서 HeapAlloc을 이용하여 Heap 영역에서 필요한 영역을 할당받습니다. 필자가 사용할 때는 사실 그냥 GlobalAlloc과 별 차이가 없었습니다.

그러나 꼭 Heap 영역을 만들고 싶을 때는 이 함수를 사용하시면 됩니다.

<4>배열에 메모리 할당. new

new 라는 연산자를 보면 새로운 메모리를 사용할 때 사용하는 C++의 메모리 관리자 입니다.  new연산자는 C++에서 사용하는 매우 편리한 메모리 관리 연산자 입니다. 필자는 많은 부분에서 new연산자를 이용합니다. 지금까지 설명한 복잡한 함수를 이용하지 않고 단순하게 new 연산자만 이용하면 해당 크기를 정확하게 설정하여 주기 때문입니다. new 연산자 처럼 메모리 연산자는 없을 것으로 봅니다. 예를 들어 정수 배열을 사용하고자 할경우가 이배열이 프로그램 제작시는 얼마나 배열이 필요한지 모르다가 프로그램이 수행되면서 배열의 크기가 설정되는 경우가 있습니다. 이럴 경우 new를 사용하여 배열을 등록하면 매우 편리합니다. 첫 번째로 포인터 연산자를 이용하여 int 의 포인터를 만듭니다.

int *data;

이 포인터에 16개의 배열을 할당하고자 한다면 다음과 같이 설정하면 됩니다.

data= new int[16];

즉 일단 포인터를 이용하여 정수형 포인터를 설정한후에 후에 new연산자를 설정하여 16개의 메모리를 할당하고 이것을 data포인터와 연결을 시키는 것입니다.

위와 같이 한후 데이터의 메모리를 해제할 경우 다음과 같이 합니다.

delete [] data;

위와 같은 방법으로 쉽게 메모리를 설정할수 있습니다. 지금까지 사용한 예제들은 모두 new를 이용하였습니다. new 함수는 특기 객체를 할당할때 유용하게 사용됩니다. 클래스를 포인터로 설정하고 이것을 메모리에 할당하고자 할때 GloabalAlloc같은 부분으로는 매우 힘듭니다. 이유는 해당 클래스의 크기를 알수가 없기 때문입니다. 이럴경우 new 연산자는 쉽게 그크기에 맞게 메모리를 할당해 줍니다. 다음은 CTestDlg라는 대화상자 클래스를 메모리에 할당하고 사용한후에 해제 하는 예입니다.

CTestDlg *pDlg;

pDlg= new CTestDlg;

pDlg->DoModal();

delete pDlg;

<5>메모리 공유하기

본항목에서는 메모리를 선언하고 이메모리를 두개이상의 모듈에서 서로 공유하여 사용하는 방법에 대해서 설명하겠습니다. 하나의 매모리 파일을 만든다음 이메모리 파일을 두개 이상의 모듈에서 공유하여 데이터를 서로 전달하고 받을때 사용하는 방법을 메모리 공유 또는 메모리 맵핑이라고 합니다. 기본적인 개념은 메모리를 파일처럼 오픈하고 한쪽에서는 데이터를 기록하고 한쪽에서는 데이터를 읽는 방식으로 데이터를 공유하는 방법입니다. 서로다른 모듈이 메모리를 공유할수 있기 때문에 서로다른 프로그램에서 데이터를 전송하고 전송받고자 할때 주로 사용하고 있습니다.

(1)메모리 공유의 방법

메모리 공유의 방법은 그림 1-10-3과 같습니다.

(그림 파일)

(그림 1-10-3)메모리 공유 방법

메모리를 공유하는하기 위해서는  매모리 맵핑파일을 만들고 이 맵핑파일에 데이터를 기록하면, 다른쪽에서는 만들어진 맵핑파일을 오픈하고 이곳에서 데이터를 읽는 방식을 이용합니다. 그림 1-10-3에서 ① 항목이 메모리 파일을 만드는 형태입니다. 이 항목과 대칭적으로 사용디는 부분이 ④ 항목입니다.①항목에서는 메모리 맵핑 파일을 만들고 ④항목에서는 메모리 맾핑 파일을 오픈하기 때문입니다. 메모리 맵핑 파일을 만드는 함수는 CreateFileMapping 파일의 기본 형태는 다음과 같습니다.

HANDLE CreateFileMapping(  HANDLE hFile, // 파일 맵핑 핸들

LPSECURITY_ATTRIBUTES lpFileMappingAttributes, //보안 속성

DWORD flProtect,           //  읽기 쓰기 옵션 플래그

DWORD dwMaximumSizeHigh,   // 상위 메모리 옵셋 최대크기

DWORD dwMaximumSizeLow,    // 하위 메모리 옵셋 최대 크기

LPCTSTR lpName             // 맵핑 파일명

);

hFile은 만들고자 하는 파일 맵핑 핸들입니다. 새롭게 만들경우 이 핸들값을 0xFFFFFFFF 로 설정합니다. 이렇게 설정하면 dwMaximumSizeHigh 와 dwMaximumSizeLow 두개의 크기에 의해서 메모리 파일 크기를 설정하고 이에 맞게 메모리 파일을 만들어줍니다. lpName에 설정할 맵핑 파일명은 매우 중요합니다. 다른 곳에서 이이름을 이용하여 매모리 맵핑 파일을 오픈하기 때문입니다.

flProtect의 값은 읽기 쓰기 를 허가하는 부분에 따라 여러값을 가질수 있습니다. PAGE_READONLY 을 설정하면 읽기전용이며 PAGE_READWRITE 이면 읽기와 쓰기가 함께 사용하는 것이 됩니다.

CreateFileMapping와 대응되는 OpenFileMapping 함수는 다음과 같습니다.

HANDLE OpenFileMapping(

DWORD dwDesiredAccess,  // 접근 모드

BOOL bInheritHandle,    // 기존 핸들사용

LPCTSTR lpName          // 맵핑 파일명

);

접근 모드는 FILE_MAP_WRITE 하면 쓰기용으로 매모리 맵핑파일을 오픈하는 것이며 FILE_MAP_READ 이면 읽기용으로 메모리 맵핑파일을 오픈하는 것입니다. 그림 1-10-3의 ④에서는 읽기와 쓰기를 모두 사용하여 메모리 맵핑 파일을 오픈하였습니다. bInheritHandle는 현재 사용하고 있는 핸들을 상속받을것인가의 플러그이니다. FALSE라고 하면 새로 만들어서 사용하게 됩니다. lpName는 맴핑파일명입니다. 이 파일명은 CreateFileMapping함수에서 설정한 lpName와 같아야 합니다. 그림 1-10-3의 ①에서 파일명을 □ExMemMap□ 라고 설정했기 때문에 ④항목에서도 "ExMemMap"라고 설정한것입니다.

이렇게 한쪽에서는 만들고 한쪽에서는 오픈하게 되면 두개의 모듈이 한개의 메모리 페이지를 공유하게 됩니다. 이렇게 공유하였을때 메모리 페이지에 데이터를 기록할 포인터를 선언해주어야 합니다. 그림 1-10-3의 ⓐ와 ⓑ에서 이 포인터를 m_pViewOf 으로 설정하였습니다. 이포인터를 메모리 페이지와 연결하기 위해서 사용하는 함수가 그림 1-10-3의 ②와 ⑤에서 사용한 MapViewOfFile 입니다. 이함수를 이용하여 m_pViewOf을 메모리 페이지에 연결하고 양쪽에서 이곳에 데이터를 수정하거나 읽거나 할경우 똑같은 데이터를 공유하게 됩니다. MapViewOfFile함수의 기본 형태는 다음과 같습니다.

LPVOID MapViewOfFile(

HANDLE hFileMappingObject,  // 맵핑 파일 핸들

DWORD dwDesiredAccess,      // 접근모드

DWORD dwFileOffsetHigh,     // 메모리 파일의 상위 옵셋 번지

DWORD dwFileOffsetLow,      // 메모리 파일의 하위 옵셋 번지

DWORD dwNumberOfBytesToMap  // 맵핑하고자 하는 크기);

hFileMappingObject는 CreateFileMapping나 OpenFileMapping의 의해서 넘겨진 핸들을 의미합니다. dwDesiredAccess 는 FILE_MAP_ 계열의 플러그를 사용합니다. FILE_MAP_READ 이면 읽기용이며,FILE_MAP_WRITE이면 쓰기용입니다. 그림 1-10-3에서는 이 두개를 OR연산을 사용하였으므로 읽기와 쓰기 모두 사용이 가능한 형태가 됩니다. 지금까지 설명한 내용 즉 CreateFileMapping와 OpenFileMapping두개의 함수로 메모리 파일을 공유하고 MapViewOfFile함수를 이용하여 한개의 데이터 포인터를 공유하게 되면 공유된 데이터 포인터에 값을 넣기만 하면 자동적으로 그값이 공유 되게 됩니다. 그림 1-10-3에서  ③항목처럼 공유된 메모리 포인터에 "nodata"라는 값을 복사하면, ⑥항목에서의 m_pViewOf에는 바로 "nodata"가 설정되게 됩니다.

(2)메모리 공유 예제 ExMemMap

ExMemMap 예제는 메모리 파일을 View와 대화상자에서 공유하고 View에서 수정된 데이터가 그대로 대화상자에도 바뀌는 내용을 예제로 작성한것입니다. 그림 1-10-4는 ExMemMap 의 전체 구조 입니다.

(그림 파일)

(그림 1-10-4)ExMemMap 의 전체 구조

그림 1-10-4에서 보듯이 ExMemMap는 SDI형태에 CFormView를 가지고 있는 형태입니다. ⓐ와 같은 메인 프로그램이 구동됨과 동시에 모달리스 대화상자가 ⓑ와 같이 출력됩니다. 이렇게 된후에 ⓐ의 에디터 박스에서 데이터를 입력하면 입력된 데이터가 그대로 ⓑ에 전송이되는 형태를 만든것입니다. 먼저 ① 항목처럼 메모리 파일을 오픈하는 부분을 CExMemmapView의 OnInitialUpdate() 함수에 다음과 같이 기록하였습니다.

void CExMemMapView::OnInitialUpdate()

{

CFormView::OnInitialUpdate();

GetParentFrame()->RecalcLayout();

ResizeParentToFit();

//메모리 파일을 만든다.

m_hMapping=CreateFileMapping((HANDLE) 0xFFFFFFFF, NULL,

PAGE_READWRITE, 0,10240, "ExMemMap");

//메로리 파일에 데이터 포인터를 m_pViewOf으로 설정

m_pViewOf = MapViewOfFile(m_hMapping,

FILE_MAP_READ|FILE_MAP_WRITE,

0, 0, 0);

//m_pViewOf에 "nodat" 설정

strcpy((char *)m_pViewOf,"nodata");

//대화상자를 모달리스 형태로 출력한다.

MDlg.Create(IDD_MAPPINGREAD,this);

MDlg.ShowWindow(SW_SHOW);

}

위와 같이 하면 메모리 파일을 만들고 대화상자를 화면에 출력하게 됩니다. 이렇게 한후 에디터박스에서 데이터 수정이 발생될때 수행되는 함수를 ClassWizard를 이용하여 만든다음 이곳에서 수정된 데이터를 m_pViewOf에 설정합니다. 이렇게 한 내용은 다음과 같습니다.

//에디터 박스의 데이터가 수정되었을경우

void CExMemMapView::OnChangeEdit()

{

//수정된 데이터를 m_pViewOf에 설정한다.

CString data;

GetDlgItem(IDC_EDIT)->GetWindowText(data);

strcpy((char *)m_pViewOf,(LPSTR)(LPCSTR)data);

}

위와 같이 하면 View부분에서 데이터를 메모리 파일에 기록하는 작업은 끝나게 됩니다. 이제 대화상자에서 메모리 파일을 오픈시키고 이곳에서 데이터를 얻는 방법을 알아보게습니다.

본예제에서 사용한 대화상자 클래스명은 CMapReadDlg 입니다. 이클래스에서 OnInitDialog함수에서는 단순하게 타이머를 0.1초로 설정합니다. 이내용은 다음과 같습니다.

BOOL CMapReadDlg::OnInitDialog()

{

CDialog::OnInitDialog();

SetTimer(1,100,NULL);

return TRUE;  // return TRUE unless you set the focus to a control

// EXCEPTION: OCX Property Pages should return FALSE

}

위와 같이하면 0.1 초마다 OnTimer함수가 수행됩니다. 이함수에서 매모리 파일을 오픈하고 데이터를 읽은다음 이데이터를 에디터박스에 설정하는 것입니다. 이내용은 다음과 같습니다.

void CMapReadDlg::OnTimer(UINT nIDEvent)

{

//메모리 맵핑 파일을 오픈한다.

m_hMapping =OpenFileMapping(FILE_MAP_READ | FILE_MAP_WRITE,

FALSE, "ExMemMap");

//m_pViewOf에 연결

m_pViewOf = MapViewOfFile(m_hMapping, FILE_MAP_READ |

FILE_MAP_WRITE, 0, 0, 0);

//m_pViewOf의 값을 data에 연결하고

CString data=(LPSTR)m_pViewOf;

//data를 에디터 박스에 설정한다.

GetDlgItem(IDC_EDIT)->SetWindowText(data);

//메모리 파일을 닫는다.

CloseHandle(m_hMapping);

CDialog::OnTimer(nIDEvent);

}

OnTimer함수가 수행되면 OpenFileMapping함수에 의해서 메모리 파일이 오픈되어지고 그리고 데이터를 읽은 다음 CloseHandle함수를 이용하여 파일을 닫습니다. 매모리 파일을 닫고자 할경우에는 CloseHandle함수를 이용하면 됩니다. CreateFile 함수를 이용하여 파일을 열었을때 닫는것과 같은 방법입니다.

위와 같이 하면 ExMemMap 프로그램이 완결됩니다. 본 예제는 View클래스와 대화상자에서 만 코딩하였습니다. View클래스와 대화상자 클래스 소스는 다음과 같습니다.

(3)CExMemMapView 클래스 소스

(프로그램 소스시작)

// ExMemMapView.h : interface of the CExMemMapView class

//

/////////////////////////////////////////////////////////////////////////////

#if !defined(AFX_EXMEMMAPVIEW_H__4A4DAC0D_3B5C_11D2_81A9_00403320A530__INCLUDED_)

#define AFX_EXMEMMAPVIEW_H__4A4DAC0D_3B5C_11D2_81A9_00403320A530__INCLUDED_

#include "MapReadDlg.h"	// Added by ClassView

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

class CExMemMapView : public CFormView

{

protected: // create from serialization only

CExMemMapView();

DECLARE_DYNCREATE(CExMemMapView)

public:

//{{AFX_DATA(CExMemMapView)

enum { IDD = IDD_EXMEMMAP_FORM };

CString	m_strData;

//}}AFX_DATA

// Attributes

public:

CExMemMapDoc* GetDocument();

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CExMemMapView)

public:

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

protected:

virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

virtual void OnInitialUpdate(); // called first time after construct

virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);

virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);

virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);

virtual void OnPrint(CDC* pDC, CPrintInfo* pInfo);

//}}AFX_VIRTUAL

// Implementation

public:

CMapReadDlg MDlg;

HANDLE m_hMapping;

LPVOID m_pViewOf;

virtual ~CExMemMapView();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:

// Generated message map functions

protected:

//{{AFX_MSG(CExMemMapView)

afx_msg void OnChangeEdit();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

#ifndef _DEBUG  // debug version in ExMemMapView.cpp

inline CExMemMapDoc* CExMemMapView::GetDocument()

{ return (CExMemMapDoc*)m_pDocument; }

#endif

/////////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_EXMEMMAPVIEW_H__4A4DAC0D_3B5C_11D2_81A9_00403320A530__INCLUDED_)

// ExMemMapView.cpp : implementation of the CExMemMapView class

//

#include "stdafx.h"

#include "ExMemMap.h"

#include "ExMemMapDoc.h"

#include "ExMemMapView.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

/////////////////////////////////////////////////////////////////////////////

// CExMemMapView

IMPLEMENT_DYNCREATE(CExMemMapView, CFormView)

BEGIN_MESSAGE_MAP(CExMemMapView, CFormView)

//{{AFX_MSG_MAP(CExMemMapView)

ON_EN_CHANGE(IDC_EDIT, OnChangeEdit)

//}}AFX_MSG_MAP

// Standard printing commands

ON_COMMAND(ID_FILE_PRINT, CFormView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_DIRECT, CFormView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_PREVIEW, CFormView::OnFilePrintPreview)

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////

// CExMemMapView construction/destruction

CExMemMapView::CExMemMapView()

: CFormView(CExMemMapView::IDD)

{

//{{AFX_DATA_INIT(CExMemMapView)

m_strData = _T("");

//}}AFX_DATA_INIT

// TODO: add construction code here

}

CExMemMapView::~CExMemMapView()

{

}

void CExMemMapView::DoDataExchange(CDataExchange* pDX)

{

CFormView::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CExMemMapView)

DDX_Text(pDX, IDC_EDIT, m_strData);

//}}AFX_DATA_MAP

}

BOOL CExMemMapView::PreCreateWindow(CREATESTRUCT& cs)

{

// TODO: Modify the Window class or styles here by modifying

//  the CREATESTRUCT cs

return CFormView::PreCreateWindow(cs);

}

void CExMemMapView::OnInitialUpdate()

{

CFormView::OnInitialUpdate();

GetParentFrame()->RecalcLayout();

ResizeParentToFit();

//메모리 파일을 만든다.

m_hMapping=CreateFileMapping((HANDLE) 0xFFFFFFFF, NULL,

PAGE_READWRITE, 0,10240, "ExMemMap");

//메로리 파일에 데이터 포인터를 m_pViewOf으로 설정

m_pViewOf = MapViewOfFile(m_hMapping, FILE_MAP_READ|FILE_MAP_WRITE,

0, 0, 0);

//m_pViewOf에 "nodat" 설정

strcpy((char *)m_pViewOf,"nodata");

//대화상자를 모달리스 형태로 출력한다.

MDlg.Create(IDD_MAPPINGREAD,this);

MDlg.ShowWindow(SW_SHOW);

}

/////////////////////////////////////////////////////////////////////////////

// CExMemMapView printing

BOOL CExMemMapView::OnPreparePrinting(CPrintInfo* pInfo)

{

// default preparation

return DoPreparePrinting(pInfo);

}

void CExMemMapView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add extra initialization before printing

}

void CExMemMapView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add cleanup after printing

}

void CExMemMapView::OnPrint(CDC* pDC, CPrintInfo* /*pInfo*/)

{

// TODO: add customized printing code here

}

/////////////////////////////////////////////////////////////////////////////

// CExMemMapView diagnostics

#ifdef _DEBUG

void CExMemMapView::AssertValid() const

{

CFormView::AssertValid();

}

void CExMemMapView::Dump(CDumpContext& dc) const

{

CFormView::Dump(dc);

}

CExMemMapDoc* CExMemMapView::GetDocument() // non-debug version is inline

{

ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CExMemMapDoc)));

return (CExMemMapDoc*)m_pDocument;

}

#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////

// CExMemMapView message handlers

//에디터 박스의 데이터가 수정되었을경우

void CExMemMapView::OnChangeEdit()

{

//수정된 데이터를 m_pViewOf에 설정한다.

CString data;

GetDlgItem(IDC_EDIT)->GetWindowText(data);

strcpy((char *)m_pViewOf,(LPSTR)(LPCSTR)data);

}

(프로그램 소스끝)

(4)CMapReadDlg 클래스 소스

(프로그램 소스시작)

#if !defined(AFX_MAPREADDLG_H__4A4DAC15_3B5C_11D2_81A9_00403320A530__INCLUDED_)

#define AFX_MAPREADDLG_H__4A4DAC15_3B5C_11D2_81A9_00403320A530__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

// MapReadDlg.h : header file

//

/////////////////////////////////////////////////////////////////////////////

// CMapReadDlg dialog

class CMapReadDlg : public CDialog

{

// Construction

public:

CMapReadDlg(CWnd* pParent = NULL);   // standard constructor

// Dialog Data

//{{AFX_DATA(CMapReadDlg)

enum { IDD = IDD_MAPPINGREAD };

// NOTE: the ClassWizard will add data members here

//}}AFX_DATA

HANDLE m_hMapping;

LPVOID m_pViewOf;

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CMapReadDlg)

protected:

virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

//}}AFX_VIRTUAL

// Implementation

protected:

// Generated message map functions

//{{AFX_MSG(CMapReadDlg)

virtual BOOL OnInitDialog();

afx_msg void OnTimer(UINT nIDEvent);

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_MAPREADDLG_H__4A4DAC15_3B5C_11D2_81A9_00403320A530__INCLUDED_)

// MapReadDlg.cpp : implementation file

//

#include "stdafx.h"

#include "ExMemMap.h"

#include "MapReadDlg.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

/////////////////////////////////////////////////////////////////////////////

// CMapReadDlg dialog

CMapReadDlg::CMapReadDlg(CWnd* pParent /*=NULL*/)

: CDialog(CMapReadDlg::IDD, pParent)

{

//{{AFX_DATA_INIT(CMapReadDlg)

// NOTE: the ClassWizard will add member initialization here

//}}AFX_DATA_INIT

}

void CMapReadDlg::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CMapReadDlg)

// NOTE: the ClassWizard will add DDX and DDV calls here

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CMapReadDlg, CDialog)

//{{AFX_MSG_MAP(CMapReadDlg)

ON_WM_TIMER()

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////

// CMapReadDlg message handlers

BOOL CMapReadDlg::OnInitDialog()

{

CDialog::OnInitDialog();

SetTimer(1,100,NULL);

return TRUE;  // return TRUE unless you set the focus to a control

// EXCEPTION: OCX Property Pages should return FALSE

}

void CMapReadDlg::OnTimer(UINT nIDEvent)

{

//메모리 맵핑 파일을 오픈한다.

m_hMapping =OpenFileMapping(FILE_MAP_READ | FILE_MAP_WRITE, FALSE, "ExMemMap");

//m_pViewOf에 연결

m_pViewOf = MapViewOfFile(m_hMapping, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);

//m_pViewOf의 값을 data에 연결하고

CString data=(LPSTR)m_pViewOf;

//data를 에디터 박스에 설정한다.

GetDlgItem(IDC_EDIT)->SetWindowText(data);

//메모리 파일을 닫는다.

CloseHandle(m_hMapping);

CDialog::OnTimer(nIDEvent);

}

(프로그램 소스끝)

2.스레드

스레드란 무엇인가? 실제로 말한다면 하나의 작업의 단위를 스레드라고 할수 있습니다. 윈도98는 멀티 테스킹이 가능한 프로그램입니다. 즉 한 번에 여러 작업을 할수 있다는 것입니다. CPU 자체는 한스텝에 한 개의 작업밖에는 하지 못합니다. 그렇다면 어떻게 윈도95는 멀티테스킹이 가능할까요? 바로 시간 분할입니다. CPU의 처리 속도가 1초에 99개의 작업 한다고 가정을 합시다. 그리고 현재 A,B,C 3개의 프로그램이 동작중이라고 예를 들어본다면 1초에 CPU는 A에 해당하는 작업 33개 B에 해당하는작업 33개 C에 해당하는 작업 33개로 나누어서 작업을 합니다.

이렇게 작업을 함으로써 작업을 멀티 테스킹으로 할수 있다라는 의미입니다. 이때 하나의 작업 단위들을 스레드라고 합니다. "그럼 프로그램 내부에 모든 작업은 스레드로 나누어 지겠네요?" 라는 질문이 있을 것 같은데 그렇지는 않습니다. 스레드란 독립적인 작업 단위입니다. 즉 다른것에 구애 받지않는 자체적인 작업 단위를 스레드라고 합니다. 윈도에서 워드 프로세서를 띄우고 대용량의 데이터를 로드하여 프린터로 출력한다고 가정을 합시다. 프린팅 작업 도중에 워드프로세서를 사용할수 있습니까? 물론 윈도 95에서는 프린터는 프린터 대로 작동을 하고 워드 프로세서는 워드 프로세서대로 작업을 할수 있습니다. 여기에 인터넷을 통해서 큰 데이터를 다운 받는다고 가정을 합시다. 프린터하고 원드프로세서로 새로운 내용을 입력하면서 인터넷을 통해서 데이터를 다운 받을수 있습니다? 물론 할수 있습니다. 여기에서 원드프로세서의 입력작업이 하나의 스레드 프린팅 작업이 하나의 스레드 또한 다운 로드가 또하나의 스레드입니다. 실지로 윈도 98의 내부적인 모든 프로그램의 동작은 멀티 스레드로 설정되어 있습니다. 그렇기 때문에 다중 작업을 할수 있는 것입니다.

프로그램 작성하다 보면 프로그램 수행도중 현재 프로그램은 다른 작업을 수행하면서 또하나의 작업을 수행하고자 할 때가 있을 것입니다. 이때 사용하는 것이 바로 스레드 입니다. 작업을 함수로 프로그래밍을 하고 난후에 이것을 스레드로 형태로 실행하면 됩니다. 이제 이렇게 스레드 형태로 만드는 방법에 대해서 설명을 하겠습니다.

<1>스레드의 구동

스레드를 구동하기 위해서는 구동할 스레드 함수를 만듭니다. 본항목에서 설명할 스레드는 윈도우 스레드가 아닌 작업스레드를 이야기 합니다. 단일 작업 스레드를 구동하고자 할경우에는 보통 다음과 같은 형으로 만듭니다.

UINT threadfunc(LPVOID lparam)

즉 함수명과 인자가 lparam으로 설정된 하나의 프로시저 함수를 만드는것입니다. 인자를 LPVOID로 한것은 어떤 형이든지 넘겨질수 있다는 의미 때문입니다. 예를 들어서 ThreadProc 스레드 함수를 만들고자 한다면 다음과 같이 할수 있습니다.

UINT ThreadProc(LPVOID lParam)

{

스레드의 내용 코딩

}

위와 같은 스레드를 동작시키고자 한다면 AfxBeginThread 함수를 이용하면 됩니다. AfxBeginThread 첫번째 인자로는 스레드 함수명이고 두번째 인자로는 lParam에 넘겨줄 데이터 포인터 입니다. 예를 들어서 CMExThreadView에서 ThreadProc를 구동하고자 한다면 다음과 같이 호출할수 있습니다.

AfxBeginThread(ThreadProc,this);

위와 같이하여서 스레드를 구동하면 lParam으로 넘겨지는 것은  현재 View의 CMExThreadView가 넘어가게 되는 것입니다.  이값을 스레드에서 받고자 한다면 다음과 같이 할수 잇을것입니다.

UINT ThreadProc(LPVOID lParam)

{

CMExThreadView *pView=(CMExThreadView *)lParam;

스레드의 내용 코딩

}

위와 같은 방법으로 lParam으로 넘어온 현재의 View포인터를 다시 pView가 형전환으로 받게 되는 것입니다. 위와 같이하면 CMexThreadView클래스이 맴버함수를 스레드에서 모두 이용할수 있는 것입니다.

보통 스레드는 루프를 돌면서 작업을 하는 경우가 대부분입니다. 루프를 돌지 않는 작업이라면 단순하게 위의 함수에 작업내용을 그대로 기록하면 됩니다. 스레드를 구종한다면 중요한것은 종료 싯점입니다. 즉 작업을 하는 도중에 작업을 멈추고자 할경우 스레드에서는 내부에서 동작을 멈추게 하기는 매우 힘듭니다. 따라서 전역변수를 하나 두고 이변수를 보면서 작업을 하는 방법이 이상적입니다.

int extflag;

UINT ThreadProc(LPVOID lParam)

{

while(!extflag)

{

스레드의 내용 코딩

}

스레드 종료후 리턴

}

위와 같이 한다면 while루프를 돌면서 exflag를 계속적으로 체크합니다. 이값이 1이 되면 바로 루프가 종료 되고 스레드의 작업이 종료 되는 형태로 변하게 됩니다.

위와 같이 하였을경우 위의 스레드를 종료 하고자 한다면 간단하게 다음과 같이 함으로써 스레드를 종료 할수가 있습니다.

extflag=TRUE;

위와 같이 함으로써 스레드 자체는 extflag의 값을 계속 검사하기 때문에 1이되면 바로 루프를 종료하게 됩니다.

<2>스레드의 구동 예제 MExThread

MExThread 는 스레드를 위한 예제 프로그램입니다. 이프로그램을 SDI형태로 제작되면 View는 CView입니다. 즉 Step1에서 Single Document Interface를 선택하신후에 Finished 를 하여서 프로젝트를 만들면됩니다.

MExThread에서는 메뉴에서 스레드 시작이라는 항목을 클릭하면 화면에 램덤한 크기에 임의의 색으로 박스를 그리는 스레드가 구동됩니다. 이스레드가 구동되면서도 다른 모든 작업을 마음대로 할수 있습니다. 이렇게 하기 위한 스레드 함수는 다음과 같습니다.

BOOL exitFlag=TRUE;

UINT ThreadProc(LPVOID lParam)

{

CBrush cBrush ,*oldBrush;

CDC *pDC;

RECT rect;

int    xLeft, xRight, yTop, yBottom, iRed, iGreen, iBlue ;

CMExThreadView *pView;

pView=(CMExThreadView *)lParam;

pView->GetClientRect(&rect);

pDC = pView->GetDC();

while (TRUE)

{

//램덤한 박스 좌표 만들기

xLeft   = rand () % rect.right ;

xRight  = rand () % rect.right ;

yTop    = rand () % rect.bottom ;

yBottom = rand () % rect.bottom;

//램덤한 색만들기

iRed    = rand () & 255 ;

iGreen  = rand () & 255 ;

iBlue   = rand () & 255 ;

//브러쉬만들기

cBrush.CreateSolidBrush (RGB (iRed, iGreen, iBlue)) ;

oldBrush=pDC->SelectObject (&cBrush) ;

//박스 그리기

pDC->Rectangle ( min (xLeft, xRight), min (yTop, yBottom),

max (xLeft, xRight), max (yTop, yBottom)) ;

pDC->SelectObject(oldBrush);

cBrush.DeleteObject();

if(exitFlag)

break;

}

pView->ReleaseDC (pDC) ;

return 0;

}

pView는 CMExThreadView 를 받고 이View의 DC를 pDC로 받은다음 View의 클라이언트 영역을 rect 로 받습니다. 이영역보다 작은 박스 영역을 rand()함수를 이용하여 임의의 영역을 만들고 R,G,B의 색을 램덤하게 만든다음 계속적으로 박스를 만드는 스레드입니다.

위의 스레드는 exitFlag가 1일경우에는 스레드가 종료 됩니다. 본함수위에 설정한 exitFlag는 TRUE 로 설정하였기 때문에 스레드를 구동하기 전에는 exitFlag를 FALSE로 설정해 주어야 합니다.

MExThread에서 스레드를 구동하는 항목과 스레드를 종료하는 항목을 설정합니다. 설정한 메뉴의 형태는 그림 1-10-5과 같습니다.

(그림 1-10-5)MExThread 메뉴에 스레드 항목 추가

스레드 시작항목의 ID는 ID_THREADBEGIN 이며 스레드 끝의 ID는 ID_THREADEND 입니다.

이와 같이 만든후에 스레드 시작이라는 항목을 선택했을때 구동하는 함수를 View에 만듭니다.(절대 CMainFrame가 아닙니다) 즉 클래스 위저드를 이용하여 클래스이름이 CMExThreadView라는 의미입니다. 이렇게 하여 만든 함수는 다음과 같습니다.

void CMExThreadView::OnThreadbegin()

{

// TODO: Add your command handler code here

exitFlag=FALSE;

AfxBeginThread(ThreadProc,(LPVOID)this);

}

exitFlag값을 0으로 넣어두고 스레드를 실행하면 while루프가 실행되기 때문에 계속적으로 화면에 박스가 램덤하게 보이게 됩니다. 메뉴에서 스레드종료 항목을 선택하였을대 구동하는 함수를 만들고 종료 루틴을 넣은 것은 다음과 같습니다.

//스레드 종료 항목 선택

void CMExThreadView::OnThreadend()

{

// TODO: Add your command handler code here

exitFlag=TRUE;

}

단지 exitFlag값을 설정함으로써 스레드를 종료 할수 있도록 한것입니다.

현재 스레드가 가동중이라면 메뉴에서 스레드 시작이라는 항목을 비활성화 해야 합니다. 또한 스레드가 멈처 있다면 메뉴에서 스레드 종료라는 항목을 비활성화 해야 합니다. 두개의 항목을 활성 비활성으로 만들기 위한 UPDATE_COMMAND_UI이벤트 함수를 만들고 다음과 같이 수정을 합니다.

//현재 스레드가 실행되면 스레드 시작 항목은 비활성

//그외 스레드 시작 항목 활성

void CMExThreadView::OnUpdateThreadbegin(CCmdUI* pCmdUI)

{

// TODO: Add your command update UI handler code here

if(exitFlag)

pCmdUI->Enable(TRUE);

else

pCmdUI->Enable(FALSE);

}

//현재 스레드가 실행되면 스레드 종료 항목은 활성

//그외 스레드 종료 항목 비활성

void CMExThreadView::OnUpdateThreadend(CCmdUI* pCmdUI)

{

if(!exitFlag)

pCmdUI->Enable(TRUE);

else

pCmdUI->Enable(FALSE);

}

exitFlag가 TRUE라는 의미는 현재 스레드가 멈추어져 있다는 의미이므로 이때는 스레드 시작 이라는 메뉴항목이 활성화 되어야 하며 exitFlag 가 FALSE 인것은 현재 스레드가 가동된다는 의미입니다. 따라서 exitFlag를 이용하여 두개의 메뉴를 활성 비활성화 할수 있는 것입니다.

위와 같이 한후에 프로그램을 컴파일하고 실행한후에 □스레드 시작□이라는 항목을 클릭하면 그림 1-10-6와 같은 결과가 출력됩니다.

(그림 1-10-6)MExThread 실행 결과

다음은 MExThread의 View클래스의 소스부분입니다. MExThread는 View클래스 에서만 코딩하였습니다. 나머지 부분은 제공되는 CD를 참조하시기 바랍니다.

// MExThreadView.cpp : implementation of the CMExThreadView class

//

#include "stdafx.h"

#include "MExThread.h"

#include "MExThreadDoc.h"

#include "MExThreadView.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

/////////////////////////////////////////////////////////////////////////////

// CMExThreadView

IMPLEMENT_DYNCREATE(CMExThreadView, CView)

BEGIN_MESSAGE_MAP(CMExThreadView, CView)

//{{AFX_MSG_MAP(CMExThreadView)

ON_COMMAND(ID_THREADBEGIN, OnThreadbegin)

ON_COMMAND(ID_THREADEND, OnThreadend)

ON_UPDATE_COMMAND_UI(ID_THREADBEGIN, OnUpdateThreadbegin)

ON_UPDATE_COMMAND_UI(ID_THREADEND, OnUpdateThreadend)

//}}AFX_MSG_MAP

// Standard printing commands

ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////

// CMExThreadView construction/destruction

BOOL exitFlag=TRUE;

UINT ThreadProc(LPVOID lParam)

{

CBrush cBrush ,*oldBrush;

CDC *pDC;

RECT rect;

int    xLeft, xRight, yTop, yBottom, iRed, iGreen, iBlue ;

CMExThreadView *pView;

pView=(CMExThreadView *)lParam;

pView->GetClientRect(&rect);

pDC = pView->GetDC();

while (TRUE)

{

xLeft   = rand () % rect.right ;

xRight  = rand () % rect.right ;

yTop    = rand () % rect.bottom ;

yBottom = rand () % rect.bottom;

iRed    = rand () & 255 ;

iGreen  = rand () & 255 ;

iBlue   = rand () & 255 ;

cBrush.CreateSolidBrush (RGB (iRed, iGreen, iBlue)) ;

oldBrush=pDC->SelectObject (&cBrush) ;

pDC->Rectangle ( min (xLeft, xRight), min (yTop, yBottom),

max (xLeft, xRight), max (yTop, yBottom)) ;

pDC->SelectObject(oldBrush);

cBrush.DeleteObject();

if(exitFlag)

break;

}

pView->ReleaseDC (pDC) ;

return 0;

}

CMExThreadView::CMExThreadView()

{

// TODO: add construction code here

}

CMExThreadView::~CMExThreadView()

{

}

BOOL CMExThreadView::PreCreateWindow(CREATESTRUCT& cs)

{

// TODO: Modify the Window class or styles here by modifying

//  the CREATESTRUCT cs

return CView::PreCreateWindow(cs);

}

/////////////////////////////////////////////////////////////////////////////

// CMExThreadView drawing

void CMExThreadView::OnDraw(CDC* pDC)

{

CMExThreadDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

// TODO: add draw code for native data here

}

/////////////////////////////////////////////////////////////////////////////

// CMExThreadView printing

BOOL CMExThreadView::OnPreparePrinting(CPrintInfo* pInfo)

{

// default preparation

return DoPreparePrinting(pInfo);

}

void CMExThreadView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add extra initialization before printing

}

void CMExThreadView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add cleanup after printing

}

/////////////////////////////////////////////////////////////////////////////

// CMExThreadView diagnostics

#ifdef _DEBUG

void CMExThreadView::AssertValid() const

{

CView::AssertValid();

}

void CMExThreadView::Dump(CDumpContext& dc) const

{

CView::Dump(dc);

}

CMExThreadDoc* CMExThreadView::GetDocument() // non-debug version is inline

{

ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMExThreadDoc)));

return (CMExThreadDoc*)m_pDocument;

}

#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////

// CMExThreadView message handlers

//스레드 시작 항목 선택

void CMExThreadView::OnThreadbegin()

{

// TODO: Add your command handler code here

exitFlag=FALSE;

AfxBeginThread(ThreadProc,(LPVOID)this);

}

//스레드 종료 항목 선택

void CMExThreadView::OnThreadend()

{

// TODO: Add your command handler code here

exitFlag=TRUE;

}

//현재 스레드가 실행되면 스레드 시작 항목은 비활성

//그외 스레드 시작 항목 활성

void CMExThreadView::OnUpdateThreadbegin(CCmdUI* pCmdUI)

{

// TODO: Add your command update UI handler code here

if(exitFlag)

pCmdUI->Enable(TRUE);

else

pCmdUI->Enable(FALSE);

}

//현재 스레드가 실행되면 스레드 종료 항목은 활성

//그외 스레드 종료 항목 비활성

void CMExThreadView::OnUpdateThreadend(CCmdUI* pCmdUI)

{

if(!exitFlag)

pCmdUI->Enable(TRUE);

else

pCmdUI->Enable(FALSE);

}

(프로그램 소스끝)

3. 스레드의 동기화

스레드를 동기화 시키는 것은 상당히 어려운 작업중의 하나입니다. 아주 작은 실수만 하더라도 프로그램은 생각과는 전혀 다른 방향으로 흘러가고 맙니다. 또한 여러개의 스레드를 동시에 디버깅 한다는 것도 쉬운일은 아닙니다. 그러나 다해이도 VC는 기본적으로 동기화가 잘 된 프로그램을 제공합니다. 작은 단위의 일을 하는 중간에 자신의 작업을 다른 스레드로 뺏기는 일이 별로 없다는 이야기입니다. 여기에서는 스레드의 동기화에 대한 맛보기 정도로만 소개합니다.

프로그램이 수행될 때 한 개의 데이터를 동시에 두 개의 프로그램이 핸들링할경우가 있습니다. 예를 들어 (a)라는 파일에 A프로그램은 데이터를 입력,수정하고 B라는 데이터는 데이터를 검색합니다. B가 읽고자 하는 시점에서 A라는 프로그램이 그데이터를 수정하고자 한다면 여기에서 문제가 발생됩니다. A가 수정한 후에 B가 읽게 할것인가? 아니면 B가 읽고 난후에 A라는 데이터가 수정을 하게 할것인가? 이런 문제점은 동시성을 가진 운영체제에서 해결해야할 중요한 항목입니다.

A스레드와 B스레드가 동시에 진행하다가 특정 사건이 발생되었을 경우 이때 B스레드는 C라는 결과가 오기 전까지는 스레드 동작을 중지 해야 합니다. 만일 중지하지 않고 현재의 스레드를 계속적으로 진행을 시킨다면 큰 문제점이 나타날것입니다. B의 상태와 C가 생각하는 B의 상태가 같아야 합니다. 이렇게 같게 맟추는 것을 동기화 라고 합니다.

MFC에서는 다음의 6가지 클래스를 멀티쓰레딩을 위해서 제공합니다.

동기 클래스인 CSyncObject, CSemaphore, CMutex, CCriticalSection, CEvent와 동기화된 접근을 허용하는 CMultiLock과 CSingleLock이 그것이니다.

이중 CSyncObject는 CSemaphore, CMutex, CCriticalSection, CEvent의 상위 클래스로서 직접 사용하지는 않습니다.

<1>크리티컬 섹션 (critical settion)

크리티컬 섹션은 같은 크리트컬 섹션을 가지고 있는 스레드 중에 한 개의 스레드 만이 작업을 할 수 있게 해 주는 것입니다. 만약 링크드 리스트를 구현 하면서 메모리에 한 개를 셀을 추가한다고 생각해 봅시다. 그리고 셀을 추가하는 것은 독립적인 스레드를 사용하여 한다고 생각합시다. (긴 파일을 관리하는 에디터 같은 경우에는 이런경우가 충분히 생길 수 있습니다. 파일을 읽어들이면서도 현재 읽혀진 부분은 화면에 표시해야하니까요) 새로운 셀을 추가하기 위해서는 새로운 메모리의 할당이 필요하고 초기화도 시켜야하고 한 개의 셀이 더 추가되었다고 알려야 합니다. 그리고 새로운 데이터도 써 넣어야 하구요. 이런 일련의 작업이 끝나기도 전에 읽기를 수행하는 스레드가 셀이 추가된 줄 알고 데이터를 읽어 가 버리면 어떻게 합니다. 이런 것을 막기 위해서 사용하는 것이 크리티컬 섹션입니다.

아래와 같은 코딩을 이용하여 크리티컬 섹션의 동작을 확인 하실 수 있습니다.

// 크리티컬 섹션 변수를 하나 선언합니다.

CCriticalSection critic;

// 첫번째 스레드 함수를 만듭니다.

UINT Thread1(LPVOIC lParam)

{

while(!bBreak)

{

critic.Lock();

// 변수의 내용을 지웁니다.

str.Empty();

for (int i = 0 ; i < 10; i++)

{

// str변수에 0 ~ 9까지의 수자를 추가하여 "012345679"를 만듭니다.

str += '0' + i;

// Sleep는 잠깐 쉬게하는 함수인데 실제로는 다른 스레드에게 제어권을

// 넘기는 것입니다. IBM PC와 같이 단일 CPU를 사용하는 경우 이 명령을

// 해 주어야 동기화가 되는 것을 확인 할 수 있습니다.

Sleep(0);

}

CString temp = "Thread1 : ";

temp += str;

cout << temp <<endl;

critic.Unlock();

}

}

// 두번째 스레드 함수를 만듭니다.

UINT Thread1(LPVOIC lParam)

{

while(!bBreak)

{

critic.Lock();

CString temp = "Thread2 : ";

temp += str;

// 현재의 str값을 출력합니다.

cout << temp <<endl;

critic.Unlock();

}

}

위의 코드를 실행시키면

Thread1 : 0123456789

Thread2 : 0123456789

가 번갈가 가면서 나올 것입니다.

여기에서 만약 critic.Lock과 critic.Unlock를 지워주면

Thread1 : 0123456789는 같지만

Thread2 : 이후의 문자열은 잘리는 경우가 만이 나올것입니다.

<2>뮤텍스 (mutex)

뮤텍스는 mutual exclusion의 약자입니다.

여러개의 쓰레드가 한개의 뮤텍스를 공유하고 이를 가지고 있는 쓰레드만 작업을 하는 것입니다. 아무리 많은 쓰레드가 있어도 뮤텍스를 공유하는 한 이중 한 개의 쓰레드만 작업을 하고 있게 되는 것입니다.

실제로는 크리티컬 섹션과 상당히 비슷하게 동작합니다. 설명을 봐도 차이를 알기가 어렵구요.

단지 차이가 있다면 크리티컬 섹션은 여러 프로세서가 동시에 접근하는 DLL과 같은 파일에서는 사용 될 수 없다는 것입니다.

뮤텍스도 사용법은 거의 같습니다 예제를 확인하세요.

<3>세마포 (semaphore)

크리티컬 섹션은 전체 쓰레드 중에 한 개의 쓰레드 만이 동작을 해서 동기화를 시켰습니다. 뮤텍스는 좀 고상하게 뮤테스를 공유하는 쓰레드 중에 한 개만 동작을 하도록 했구요. 이걸 조금더 발전시켜서 한개만이 아니라 원하는 개수의 쓰레드가 동시에 작업 할 수 있도록 한것이 세마포입니다. 만일 2개를 까지를 데이터를 공유할수 있게 한다면 클래스를 설정할 경우 다음과 같이 합니다.

CSemaphore* semaphore;//세마포

//2개 세마포 설정

semaphore = new CSemaphore(1,2);

첫 번째 인자 1은 시작 스레드 번호이며 두 번째 인자 2는 최대 몇 개까지의 스레드가 공유할수 있는가의 값입니다. 현재 2로 설정되어 있으니까 최대 두 개의 스레드가 데이터를 공유할수 있습니다.

세마포는 위의 예를 사용하지 않고 다른 예를 사용하겠습니다. 공유하는 데이터가 함수라고 가정하고 그함수 이름을 UseData라고 하였습니다.

//세마포가 공유하는 함수

void UseData(LPVOID param,int num)

{

char temp[80];

CSingleLock singleLock(semaphore);

singleLock.Lock();

sprintf(temp,"번호 %d 스레드가 데이터를 점유!",num);

::MessageBox((HWND)param,temp,"세마포",MB_OK);

}

이제 UseData를 공유하는 3개의 스레드를 만듭니다.

UINT SemephoreProc1(LPVOID param)

{

UseData(param,1);

return 0;

}

UINT SemephoreProc2(LPVOID param)

{

UseData(param,2);

return 0;

}

UINT SemephoreProc3(LPVOID param)

{

UseData(param,3);

return 0;

}

위의 3개의 스레드를 동시에 다음과 같이 구동을 하면

AfxBeginThread(SemephoreProc1,GetSafeHwnd());

AfxBeginThread(SemephoreProc2,GetSafeHwnd());

AfxBeginThread(SemephoreProc3,GetSafeHwnd());

두 개 까지는 구동이 되고 나머지3번째 SemephoreProc3는 기다립니다. 하나의 스레드가 종료 될 때까지요. 즉 처음에 설정할 때 최대 2개를 설정하였기 때문입니다.

<4>이벤트

이벤트는 언제 작업이 이루어 졌는지를 알기를 원할 때 유용한 방법입니다.

예들들어 통신을 생각해 보도록 하겠습니다. 윈도95에서는 시리얼포트를 파일의 개념으로 사용합니다. 데이터를 파일로 보내는 경우는 상당히 빠른 속도로 이루어 지지만 이 데이터를 시리얼 포트를 사용하여 내보내는 데는 시간이 걸립니다. 그래서 데이터를 내보내고 또 내보내려 한다면 이전의 데이터가 다 전송이 되었는지를 확인할 필요가 있습니다. 이런경우에 이벤트라는 개념을 사용합니다. 윈도의 탐색기에서 열려있는 폴더의 내용이 바뀌면 다시 그 내용이 업데이트 되는데 이것을 감시하는데도 사용될 수 있습니다.

사용할 동기화 클래스의 선택

어떤 동기화 클래스를 상용할 것인지를 결정하기 위해서는 다음과 같은 내용을 고려해 보아야 합니다.

1. 프로그램이 어떤 리소스에 접근하기 위해서 다른 일이 발새하기를 기다릴 필요가 있는가(예를 들어서 통신포트를 통해서 들어온 데이터를 받기위해서는 이전에 이 데이터가 파일에 써저야 한다)?

그렇다면 CEvent를 사용합니다.

2. 동시에 여러개의 클래스가 한개의 리소스에 접근할 필요가 있는가(예를 들자면 5개 까지의 클래스가 리소스에 접근 할 수 있도록 허락한다)?

그렇다면 CSemaphore를 사용합니다.

3. 한개 이상의 프로그램이 리소스에 접근하는가(예를 들면 DLL등에서 사용된다)?

그렇다면 CMutex을 사용합니다.

아니면 CCriticalSection을 사용합니다.

<5>스레드의 동기화 예제 MThread

여기서는 크리티컬 섹션과 뮤텍스의 동기화 예제를 보여줍니다. MakeString 스레드와 DisplayString 스레드를 사요합니다. MakeString 스레드는 01234567890의 문자열을  만드는 스레드이고 DisplayString 스레드는 만드는 문자열을 리스트 박스에 디스플레이 시켜주는 스레드입니다.

각각의 결과는 그림 1-10-7, 그림 1-10-8, 그림 1-10-9와 같이 나타납니다.

(그림 1-10-7) 동기화를 하지 않았을 때

(그림 1-10-8) Critical section을 사용했을 때

(그림 1-10-9) 뮤텍스를 사용했을 때

MThread예제는 실제 프로그램을 소스를 보시면서 이해하시기 바랍니다. 전항목에서 설명한 내용을 그대로 이용한 예제입니다. 다음은 MThread의 MThreadDlg 클래스의 소스입니다.

(프로그램 소스시작)

// MThreadDlg.h : header file

//

#if !defined(AFX_MTHREADDLG_H__DCF66808_3255_11D1_823C_444553540000__INCLUDED_)

#define AFX_MTHREADDLG_H__DCF66808_3255_11D1_823C_444553540000__INCLUDED_

#if _MSC_VER >= 1000

#pragma once

#endif // _MSC_VER >= 1000

/////////////////////////////////////////////////////////////////////////////

// CMThreadDlg dialog

class CMThreadDlg : public CDialog

{

// Construction

public:

CMThreadDlg(CWnd* pParent = NULL);	// standard constructor

CWinThread *m_thread[2];

// Dialog Data

//{{AFX_DATA(CMThreadDlg)

enum { IDD = IDD_MTHREAD_DIALOG };

CListBox	m_list;

int	m_nMethod;

BOOL	m_bStop;

//}}AFX_DATA

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CMThreadDlg)

protected:

virtual void DoDataExchange(CDataExchange* pDX);	// DDX/DDV support

//}}AFX_VIRTUAL

// Implementation

public:

CString m_str;

void  Display(CString &str);

protected:

HICON m_hIcon;

// Generated message map functions

//{{AFX_MSG(CMThreadDlg)

virtual BOOL OnInitDialog();

afx_msg void OnSysCommand(UINT nID, LPARAM lParam);

afx_msg void OnPaint();

afx_msg HCURSOR OnQueryDragIcon();

afx_msg void OnStop();

afx_msg void OnDlgUpdate();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

//{{AFX_INSERT_LOCATION}}

// Microsoft Developer Studio will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_MTHREADDLG_H__DCF66808_3255_11D1_823C_444553540000__INCLUDED_)

MThreadDlg.cpp

// MThreadDlg.cpp : implementation file

//

#include "stdafx.h"

#include "MThread.h"

#include "MThreadDlg.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

/////////////////////////////////////////////////////////////////////////////

// CAboutDlg dialog used for App About

UINT MakeString(LPVOID lParam);

UINT DisplayString(LPVOID lParam);

class CAboutDlg : public CDialog

{

public:

CAboutDlg();

// Dialog Data

//{{AFX_DATA(CAboutDlg)

enum { IDD = IDD_ABOUTBOX };

//}}AFX_DATA

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CAboutDlg)

protected:

virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

//}}AFX_VIRTUAL

// Implementation

protected:

//{{AFX_MSG(CAboutDlg)

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)

{

//{{AFX_DATA_INIT(CAboutDlg)

//}}AFX_DATA_INIT

}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CAboutDlg)

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)

//{{AFX_MSG_MAP(CAboutDlg)

// No message handlers

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////

// CMThreadDlg dialog

CMThreadDlg::CMThreadDlg(CWnd* pParent /*=NULL*/)

: CDialog(CMThreadDlg::IDD, pParent)

{

//{{AFX_DATA_INIT(CMThreadDlg)

m_nMethod = 0;

m_bStop = FALSE;

//}}AFX_DATA_INIT

// Note that LoadIcon does not require a subsequent DestroyIcon in Win32

m_str = _T("0000000");

m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);

}

void CMThreadDlg::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CMThreadDlg)

DDX_Control(pDX, IDC_LIST1, m_list);

DDX_Radio(pDX, IDC_RADIO1, m_nMethod);

DDX_Check(pDX, IDC_STOP, m_bStop);

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CMThreadDlg, CDialog)

//{{AFX_MSG_MAP(CMThreadDlg)

ON_WM_SYSCOMMAND()

ON_WM_PAINT()

ON_WM_QUERYDRAGICON()

ON_BN_CLICKED(IDC_STOP, OnStop)

ON_BN_CLICKED(IDC_RADIO1, OnDlgUpdate)

ON_BN_CLICKED(IDC_RADIO2, OnDlgUpdate)

ON_BN_CLICKED(IDC_RADIO3, OnDlgUpdate)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////

// CMThreadDlg message handlers

BOOL CMThreadDlg::OnInitDialog()

{

CDialog::OnInitDialog();

// Add "About..." menu item to system menu.

// IDM_ABOUTBOX must be in the system command range.

ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);

ASSERT(IDM_ABOUTBOX < 0xF000);

CMenu* pSysMenu = GetSystemMenu(FALSE);

if (pSysMenu != NULL)

{

CString strAboutMenu;

strAboutMenu.LoadString(IDS_ABOUTBOX);

if (!strAboutMenu.IsEmpty())

{

pSysMenu->AppendMenu(MF_SEPARATOR);

pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);

}

}

// Set the icon for this dialog.  The framework does this automatically

//  when the application's main window is not a dialog

SetIcon(m_hIcon, TRUE);	// Set big icon

SetIcon(m_hIcon, FALSE);	// Set small icon

// TODO: Add extra initialization here

m_thread[0] = AfxBeginThread(MakeString, this);

m_thread[1] = AfxBeginThread(DisplayString, this);

return TRUE;  // return TRUE  unless you set the focus to a control

}

void CMThreadDlg::OnSysCommand(UINT nID, LPARAM lParam)

{

if ((nID & 0xFFF0) == IDM_ABOUTBOX)

{

CAboutDlg dlgAbout;

dlgAbout.DoModal();

}

else

{

CDialog::OnSysCommand(nID, lParam);

}

}

// If you add a minimize button to your dialog, you will need the code below

//  to draw the icon.  For MFC applications using the document/view model,

//  this is automatically done for you by the framework.

void CMThreadDlg::OnPaint()

{

if (IsIconic())

{

CPaintDC dc(this); // device context for painting

SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

// Center icon in client rectangle

int cxIcon = GetSystemMetrics(SM_CXICON);

int cyIcon = GetSystemMetrics(SM_CYICON);

CRect rect;

GetClientRect(&rect);

int x = (rect.Width() - cxIcon + 1) / 2;

int y = (rect.Height() - cyIcon + 1) / 2;

// Draw the icon

dc.DrawIcon(x, y, m_hIcon);

}

else

{

CDialog::OnPaint();

}

}

// The system calls this to obtain the cursor to display while the user drags

//  the minimized window.

HCURSOR CMThreadDlg::OnQueryDragIcon()

{

return (HCURSOR) m_hIcon;

}

void CMThreadDlg::OnStop()

{

UpdateData();

if ( m_bStop == FALSE )

{

m_thread[0]->ResumeThread();

m_thread[1]->ResumeThread();

}

else

{

m_thread[0]->SuspendThread();

m_thread[1]->SuspendThread();

}

}

void CMThreadDlg::OnDlgUpdate()

{

UpdateData();

}

void CMThreadDlg::Display(CString &str)

{

int x = m_list.AddString(str);

m_list.SetCurSel(x);

if (m_list.GetCount() > 100)

m_list.DeleteString(0);

}

//Thread.cpp

#include "stdafx.h"

#include "afxmt.h"

#include "mthread.h"

#include "mthreaddlg.h"

CCriticalSection critic;

CMutex mutex;

UINT MakeString(LPVOID lParam)

{

CMThreadDlg* pOwner = (CMThreadDlg*) lParam ;

if ( pOwner == NULL )

{

AfxMessageBox("쓰레드를 실행시키는데 실패하였습니다");

return 0;

}

CSingleLock sing(&mutex);

while (!pOwner->m_bStop)

{

int nMethod = pOwner->m_nMethod;

if ( nMethod == 1)

critic.Lock();

if ( nMethod == 2)

sing.Lock();

pOwner->m_str.Empty();

for (int i = 0 ; i < 10 ; i++)

{

pOwner->m_str += '0'+ i;

Sleep(0);

}

CString str = CString("Make : ") + pOwner->m_str;

pOwner->Display(str);

if ( nMethod == 1)

critic.Unlock();

if ( nMethod == 2)

sing.Unlock();

}

return TRUE;

}

UINT DisplayString(LPVOID lParam)

{

CMThreadDlg* pOwner = (CMThreadDlg*) lParam ;

if ( pOwner == NULL )

{

AfxMessageBox("쓰레드를 실행시키는데 실패하였습니다");

return 0;

}

CSingleLock sing(&mutex);

while (!pOwner->m_bStop)

{

int nMethod = pOwner->m_nMethod;

if ( nMethod == 1)

critic.Lock();

if ( nMethod == 2)

sing.Lock();

CString str = CString("Disp : ") + pOwner->m_str;

pOwner->Display(str);

if ( nMethod == 1)

critic.Unlock();

if ( nMethod == 2)

sing.Unlock();

}

return TRUE;

}

(프로그램 소스끝)