1. 단일문서 기반의 프로젝트 생성(Duplex)

 

2. Duplex.h파일(어플리케이션 클래스)에 핸들을 받을 수 있는 변수 선언

-중복실행을 방지하기 위한 것이므로, 실행되기 전에 체크하여 실행되는것 자체를 취소시키기 위해 어플리케이션 클래스에 선언함

class CDuplexApp : public CWinApp
{
public:
     CDuplexApp();
     //CEvent m_Event;
     HANDLE m_hDupCheck; // 핸들을 받을 수 있는 변수 선언

 

3. 어플리케이션클래스(Duplex.cpp)의 InitInstance()함수에 중복실행을 방지하기 위한 코드 작성

아무것도 실행할 필요가 없으므로 바로 비교하고 종료될 수 있도록 맨 위쪽에 코드를 작성해 준다.

 

BOOL CDuplexApp::InitInstance()
{
     ::CreateEvent(NULL, FALSE, FALSE, TEXT("DUPCHECK_THISAPP_EVENT"));
     if (::GetLastError()==ERROR_ALREADY_EXISTS)
     {
          AfxMessageBox(_T("ERROR : Application is already running!"));
          return FALSE; // InitInstance함수가 실행되지 않고 빠져나온다. 이 프로그램은 메모리에 올라가지 않는다.
     }

.

.

.

.

.

 


'PROGRAMMING > MFC(C++)' 카테고리의 다른 글

[0425수업] ClipBoard  (0) 2011.04.25
[0422수업] IPC통신  (0) 2011.04.22
[0421수업] 소프트웨어 업데이트  (0) 2011.04.21
[0421수업] 세마포어  (0) 2011.04.21
[0418수업] ThreadCrash(Critical Section, 뮤텍스)  (0) 2011.04.18
Posted by 마마필로 :

2개의 프로젝트를 만들어 소프트웨어 업데이트 하기

 

1. 단일문서 기반의 첫번째 프로젝트(Update) 생성

Update.h파일에 이벤트 선언

class CUpdateApp : public CWinApp
{
public:
     CUpdateApp()

     CEvent m_ExitEvent;
     CEvent m_UpdateEvent;

 

Update.cpp파일에 생성 코드 추가

CUpdateApp::CUpdateApp()
:m_ExitEvent(FALSE, TRUE), m_UpdateEvent(FALSE, TRUE, TEXT("UPDATE_SW_EVENT"))
{

     // TODO: 여기에 생성 코드를 추가합니다.
     // InitInstance에 모든 중요한 초기화 작업을 배치합니다.
}

 

2. UpdateApp클래스에 함수 추가

UINT CUpdateApp::ThreadUpdate(LPVOID pParam)
{
     return 0;
}

 

 

//소프트웨어 업데이트를 위한 코드 작성: 소프트웨어 업데이트에서 거의 정형화된 코드이므로 숙지. 외워도 좋음

UINT CUpdateApp::ThreadUpdate(LPVOID pParam)
{
     HANDLE arhList[2];
     arhList[0]=(HANDLE)theApp.m_ExitEvent;
     arhList[1]=(HANDLE)theApp.m_UpdateEvent;

 

     DWORD dwResult=::WaitForMultipleObjects(2, arhList, FALSE, INFINITE);
     if (dwResult==WAIT_OBJECT_0+1)
     {
      CWnd* pMain=theApp.m_pMainWnd;
          if (pMain != NULL) // NULL이면 윈도우가 없고, !=NULL 윈도우가 있다는 것
               pMain->PostMessage(WM_CLOSE);
     }
 return 0;
}

3. Update.cpp에 스레드가 시작할 수 있게 해줌

AfxBeginThread(CUpdateApp::ThreadUpdate,NULL); // InitInstance()함수가 시작되면서 스레드가 돌기 시작함

 

4. UpdapteApp클래스에서 ExitInstance()함수 재정의

int CUpdateApp::ExitInstance()
{
     m_ExitEvent.SetEvent();
     ::Sleep(100);

 

     return CWinApp::ExitInstance();
}

 

 


 

 

5. 대화상자 기반의 두번째 프로젝트(SetUpdate) 생성

 

6. 버튼추가, 캡션 수정(Button1 -> Set UpdateEvent)

 

7. 버튼을 더블클릭하여 이벤트 처리기 생성

void CSetUpdateDlg::OnBnClickedButton1()
{
     CEvent SetUpdateEvent(FALSE, TRUE, _T("UPDATE_SW_EVENT")); // 같은 이벤트 이름으로 설정(UPDATE_SW_EVENT)
     SetUpdateEvent.SetEvent();
     ::Sleep(500);
}

 

 

cf.) 두 프로젝트 모두 <afxmt.h> 인클루드 해주어야 함.

 

<실제 소프트웨어 업데이트에서 이용>

예를 들어 익스플로러를 업데이트 할때 이미 실행되어 있는 이전 버전의 익스플로러 여러개를

업데이트 전에 한번에 다 닫아준 후 업데이트를 시작한다.

이 코드를 실행 후 업데이트를 위한 ShellExecute가 실행된다.

 

프로그램의 중복실행 방지


Posted by 마마필로 :

예를 들어 8개의 스레드가 돌고 있는데 모두 같은 리소스를 사용한다고 가정 하면 이 스레드들은 레이싱 컨디션에 들어간다.

 

이 때, 크리티컬 섹션/뮤텍스를 사용하면 7개를 스탑시키고 한개를 리소스에 접근시켜야 함

그러나 세마포어는 2개이상의 스레드를 접근시키고 나머지는 대기할 수 있도록 하는 것이 가능하다.

 

따라서 크리티컬 섹션/뮤텍스는 두 개의 스레드를 교차 실행 시켜야 할 때.

 

1. 프로젝트 생성, 단일문서기반,  (서비스팩이 설치되어 있다면 오피스 2003)

 

2. stdafx.cpp

CString g_arString[10];
CSemaphore g_SP(3,3);

 

stdafx.h에 <afxmt.h>인클루드

 

extern CString g_arString[10];
extern CSemaphore g_SP;

 

3. Semaphore.h파일의 CwinApp클래스에 CEvent 객체 추가

 

class CSemaphoreApp : public CWinApp
{
 public:
     CSemaphoreApp();
     CEvent m_ExitEvent;    // 종료이벤트

 

 

객체 초기화-> 생성자를 부른다.어플리케이션 클래스의 생성자 함수 안에서 초기화

CSemaphoreApp::CSemaphoreApp()
:m_ExitEvent(FALSE, TRUE)
{
     // TODO: 여기에 생성 코드를 추가합니다.
     // InitInstance에 모든 중요한 초기화 작업을 배치합니다.
}

 

4. SemaphoreApp에 ExitInstance() 함수 재정의

int CSemaphoreApp::ExitInstance()
{
     m_ExitEvent.SetEvent();

     ::Sleep(1000);

 

     return CWinApp::ExitInstance();
}

5. 특정 클래스 안에 멤버함수로 스레드 만들기(지난번에 한것은 전역함수로 스레드를 만듬)

SemaphoreApp클래스 속성에서 함수 추가

반환 형식: UINT

매개 변수 형식: LPVOID

함수 이름: threadSemaphore

액세스: public

매개 변수 이름: pParam 추가

static(정적함수) 생성

주석: App 클래스에 멤버로서 포함되어진 스레드

 

-> 실제 함수 추가시에 직접 코드를 작성하는 것이 더 편할 수 있다.

하지만 마법사를 이용할경우 헤더파일에 선언부를 자동으로 함께 만들어준다는 장점이 있다.

 

UINT CSemaphoreApp::ThreadSemaphore(LPVOID pParam)
{
     CString strTmp = _T("");
     int nIndex = (int)(pParam);

     

     g_arString[nIndex] = _T("");
     while(::WaitForSingleObject(theApp.m_ExitEvent, 10) == WAIT_TIMEOUT)
     {
         g_arString[nIndex].Format(TEXT("%d Thread is waiting!!"), nIndex);

  

         g_SP.Lock();
         g_arString[nIndex].Format(TEXT("##%d Thread is running!!"), nIndex);
         ::Sleep(100);
         g_SP.Unlock();
     }
     return 0;
}

 

6. Semaphore.cpp에

 for(int i=0; i<10; ++i)
 {
      AfxBeginThread(CSemaphoreApp::ThreadSemaphore, (LPVOID)i);
 }

 

7. 화면에 뭔가 보여주기 위한 작업

뷰클래스에 WM_Create메시지 추가, 여기에 타이머 설정

int CSemaphoreView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
     if (CView::OnCreate(lpCreateStruct) == -1)
          return -1;

     

     SetTimer(100, 1000, NULL);
     // TODO:  여기에 특수화된 작성 코드를 추가합니다.

     

     return 0;
}

 


뷰클래스에 WM_Timer메시지 추가

void CSemaphoreView::OnTimer(UINT_PTR nIDEvent)
{
     CClientDC dc(this);
     dc.FillSolidRect(0, 0, 300, 300, RGB(255, 255, 255));
     for (int i=0;i<10;i++)
     {
          if(g_arString[i].GetAt(0) == '#')
               dc.SetTextColor(RGB(192, 0, 0));
          else
               dc.SetTextColor(RGB(192, 192, 192));

  

         dc.TextOut(30, 20 * i + 10, g_arString[i]);
 }

 

 CView::OnTimer(nIDEvent);
}

 

 

OS가(커널) 스레드를 대기시키고 다른 스레드를 실행하는 기준?

UINT CSemaphoreApp::ThreadSemaphore(LPVOID pParam)
{
     CString strTmp = _T("");
     int nIndex = (int)(pParam);

     g_arString[nIndex] = _T("");
 

     while(::WaitForSingleObject(theApp.m_ExitEvent, 10) == WAIT_TIMEOUT)
     {
          g_arString[nIndex].Format(TEXT("%d Thread is waiting!!"), nIndex);

          g_SP.Lock();
          g_arString[nIndex].Format(TEXT("##%d Thread is running!!"), nIndex);
          ::Sleep(100);
          g_SP.Unlock();
     }
     return 0;

}

 

--> 여기서 while문의 이해가 중요

 

/*다음의 의미를 구별*/

이벤트: 키보드/마우스의 움직임과 같은 행위 그 자체. 행위를 나타내는 일종의 신호(데이터)

메시지:

이벤트 처리기:

메시지 처리기(메시지 처리 함수): 이벤트의 신호에 대응해서 큐에 들어온 메시지를 처리하는 함수

<크리티컬섹션/뮤텍스/세마포어>

스레드를 멈추는것이 목적이 아님.

실행되고 있는 스레드끼리 부딪히는것을 막음.


Posted by 마마필로 :

1. 대화상자 기반의 새 프로젝트

 

2. 버튼 세개 추가

캡션 변경(Begin Thread1, Begin Thread2, Call GetString()

 

3. 이벤트 핸들러 추가. ThreadCrashDlg.cpp에 코드 작성

 

4. stdafx.cpp에 전역변수 선언

CCriticalSection g_CS;
TCHAR* g_pszBuffer = NULL; // 널종료 문자열, 문자 한자를 임시로 대기시키기 위한 공간

 

void SetString(TCHAR* pszData)
{
     // g_CS.Lock();
     int nLength = lstrlen(pszData)+1;  // 문자열의 길이값을 받아와서 기존의 값에 +1
     if(g_pszBuffer != NULL)
          free(g_pszBuffer);

 

     g_pszBuffer = (TCHAR*)malloc(sizeof(TCHAR)*nLength);
     swprintf_s(g_pszBuffer, nLength, TEXT("%s"), pszData);

 

     // g_CS.Unlock();

}

 

BOOL GetString(TCHAR* pszData)
{
     // g_CS.Lock();
 

     if(pszData != NULL && g_pszBuffer != NULL)
     {
          int nLength = lstrlen(g_pszBuffer);
          swprintf_s(pszData, nLength+1, TEXT("%s"), g_pszBuffer);
  

         // g_CS.Unlock();
  

         return FALSE;
     }
 

     // g_CS.Unlock();
     return TRUE;
}

 

g_CS.Lock() // 외부 스레드가 이 데이터에 접근하는 것을 막음

g_CS.Unlock() //

 

SetString->트랜젝션 하는 것

GetString->리트리브 하는 것(데이터를 얻는것)

트랜젝션: (프로그래밍)데이터를 쓰거나 지우거나 삭제하거나 수정하거나 생성하는 행위

 

5. stdafx.cpp에 스레드문 작성

UINT ThreadFunc1(LPVOID pParam)
{
     while(TRUE)
     {
          ::Sleep(1);
          SetString(TEXT("ThreadFun1"));
     }
 return 0;
}

 

UINT ThreadFunc2(LPVOID pParam)
{
     while(TRUE)
     {
          ::Sleep(1);
          SetString((TEXT("ThreadFun2")));
     }
     return 0;
}

6.stdafx.h파일에 원형 선언

extern TCHAR* g_pszBuffer;

 

void SetString(TCHAR* pszData);
BOOL GetString(TCHAR* pszData);
UINT ThreadFunc1(LPVOID pParam);
UINT ThreadFunc2(LPVOID pParam);

 

7. Button3이벤트 처리기 작성

void CThreadCrashDlg::OnBnClickedButton3()
{
     TCHAR szBuffer[64];
     ::ZeroMemory(szBuffer, sizeof(szBuffer));

     if(GetString(szBuffer))
          AfxMessageBox(szBuffer);

}

 

이 코드내에서 Lock/Unlock의 역할

Lock함수 내에서 실제로 Suspend Thread함수가 구현되어 있고,

반대로 Unlock은 Resume Thread로 구현되어 있다.

 

주석처리한 g_CS.Lock/g_CS.Unlock를 풀고 stdafx.h파일에 afxmt.h파일을 인클루드 하고 컴파일하여 실행

Posted by 마마필로 :

원문: http://sol9501.blog.me/70104141478

|소켓 프로그래밍| 멀티 스레드(스레드 동기화) ③

 

* 이번 포스팅은 멀티스레드를 사용하는 경우 두 개 이상의 스레드가 공유 데이터를 접근하는 경우 발생하는 문제를 알아보고 이를 해결하고자 사용하는 동기화 기법인 임계 영역, 뮤텍스, 이벤트, 세마포어등을 알아보겠습니다.

 

 

1. 동기화의 필요성

- 다음의 경우 멀티스레드 사용시 스레드 동기화가 필요하다.

 

● 두개 이상의 스레드가 공유 리소스를 접근할때, 오직 한 스레드만 접근을 허용해야 하는 경우.

 

● 특정 사건 발생을 다른 스레드에게 알리는 경우. 예를 들면, 한 스레드가 작업을 완료한후, 대기중인 다른 스레드를 깨우는 경우

 

간단히 다음의 그림 1을 보면서 설명하겠습니다.

 

그림 1) 스레드 동기화

 

 

- 위의 그림 1과 같은 경우 Thread1과 Thread2는 동시에 공유 변수 5에 접근하고 있습니다. 이 경우 Thread1은 공유 변수 5를 읽어 들여 1을 더하는 작업을 하고 그 결과 값 6을 공유 변수에 다시 쓰고 있습니다. Thread2도 공유 변수 5를 가져와 +1을 하고 그 값을 다시 공유 변수에 쓰고 있습니다. 이 경우 유저는 양쪽의 스레드에서 모두 +1을 했으니 +7의 값을 기대하고 있을 것 입니다. 즉 실제적으로 유저가 기대한 그림은 다음 그림 2와 같은 시나리오 일 것입니다.

 

 

그림 2) 동기화 적용

 

 

 

- 위와 같이 공유 변수에 대한 접근을 하나의 스레드로 제한 함으로써 정확한 값을 리턴 받을 수 있습니다. 즉 이와 같이 멀티스레드 환경에서 발생하는 문제를 해결하기 위한 일련의 작업을 스레드 동기화(Thread Synchronization)라 부릅니다. 윈도우는 다양한 동기화 관련 API를 제공하여 프로그래머가 상황에 따라 적절한 동기화 방법을 선택할 수 있도록 하고 있습니다. 이러한 스레드 동기화 기법은 다음 표1과 같습니다.

 

[표 1] - 스레드 동기화 기법

종류

용도

임계 영역(critical section)

* 공유 리소스에 대해 오직 하나의 스레드 접근만 허용한다.(한 프로세스에 속한 스레드에만 사용 가능)

뮤텍스(mutex)

* 공유 리소스에 대해 오직 하나의 스레드 접근만 허용한다.(서로 다른 프로세스에 속한 스레드에도 사용 가능)

이벤트(event)

* 특정 사건 발생을 다른 스레드에 알린다.

세마포어(semaphore)

* 한정된 개수의 자원을 여러 스레드가 사용하려고 할 때 접근을 제한한다.

 

* 동기화에 대한 더 자세한 내용은 제프리 리처의 "WINDOWS VIA C/C++"나 정덕영 저 "윈도우 구조와 원리"를 추천합니다.

 

 

2. 임계영역

- 임계역역은 두 개 이상의 스레드가 공유 리소스를 접근할 때 오직 한 스레드 접근만 허용해야 하는 경우에 사용한다. 임계 역역은 스레드 동기화를 위해 사용하지만 동기화 객체로 분류하지 않으며 특징 또한 다르다. 대표적인 특징은 다음과 같습니다.

 

● 임계 영역은 유저 영역 메모리에 존재하는 구조체다. 따라서 다른 프로세스가 접근 할 수 없으므로 한 프로세스에 속한 스레드 동기화에만 사용할 수 있다.

● 일반적인 동기화 객체보다 빠르고 효율적이다.

 

임계 영역을 사용을 확인하기 위해 간단한 소스를 작성해 보겠습니다.

 

 

 

- 위의 소스를 그대로 실행하면 hThread[0]과 hThread[1]이 공유 변수 val을 한 번씩 점유해 hThread[0]은 앞에서부터 채워나가고 hThread[1]은 뒤에서부터 값을 채워나가니 결과적으로 배열에는 값 9, 3 이 대략 반반씩 채워지게 됩니다. 결과화면은 그림 3과 같습니다.

 

그림 3) 일반적인 실행화면

 

 

 

- 그렇다면 이번에는 임계 영역을 사용하여 하나의 스레드만이 공유 리소스를 점유하도록 해보겠습니다.

 

우선 CRITICAL_SECTION 구조체 변수를 전역 변수를 선언합니다.

임계 영역을 사용하기 전에 InitializeCriticalSection()함수를 호출하여 초기화 합니다.

공유 리소스를 사용하기 전에 EnterCriticalSection() 함수를 호출합니다. 공유 리소스를 사용하고 있는 스레드가 없다면 EnterCriticalSecrion() 함수는 곧바로 리턴합니다. 공유 리소스를 사용하고 있는 스레드가 있다면 EnterCriticalSecrion() 함수는 리턴하지 않고 해당 스레드는 대기 상태가됩니다.

공유 리소스 사용이 끝나면 LeaveCriticalSection() 함수를 호출합니다.

임계 영역 사용이 끝나면 DeleteCriticalSection() 함수를 호출합니다.

 

 

실행화면은 다음 그림 4와 같습니다. 소스는 위의 소스 1의 주석부분만 풀어주시면 됩니다.

 

그림 4) 임계 영역사용

 

 

 

3. 이벤트

- 이벤트는 특정 사건의 발생을 다른 스레드에 알리는 경우에 주로 사용합니다. 서두에 동기화의 필요성에서

 

● 특정 사건 발생을 다른 스레드에게 알리는 경우. 예를 들면, 한 스레드가 작업을 완료한 후, 대기중인 다른 스레드를 깨우는 경우

 

동기화가 필요하다고 했습니다. 즉 한 스레드가 작업을 완료한 후 대기 중인 다른 스레드가 깨어나서 진행하는 시나리오를 만들 때 이벤트를 이용합니다. 이벤트의 동기화의 간단한 과정은 다음과 같습니다.

 

1. 이벤트를 비신호 상태로 생성한다.

2. 한 스레드가 작업을 진행하고 나머지 스레드는 이벤트에 대해 Wait*()함수를 호출 함으로써 이벤트가 신호 상태가 되기를 기다린다.

3. 스레드가 작업을 완료하면 이벤트를 신호 상태로 바꾼다.

4. 기다리고 있던 모든 스레드가 깨어나서 작업을 진행한다.

 

- 이벤트는 대표적인 동기화 객체로 신호와 비신호라는 두 가지 상태를 가지며 상태를 변경 할 수 있도록 다음과 같은 함수를 제공한다.

 

1) SetEvent

형식 :

Bool SetEvent(HANDEL hEvent);

비신호 상태 - > 신호 상태

 

2) ResetEvent

형식 :

Bool ResetEvent(HANDEL hEvent);

신호 상태 - > 비신호 상태

 

3) CreateEvent

형식 :

HANDLE WINAPI CreateEvent(

__in_opt  LPSECURITY_ATTRIBUTES lpEventAttributes,

__in      BOOL bManualReset,

__in      BOOL bInitialState,

__in_opt  LPCTSTR lpName

);

파라미터    

lpEventAttributes

* SECURITY_ATTRIBUTES 구조체 변수의 주소값을 대입한다. SECURITY_ATTRIBUTES 구조체는 핸들 상속과 보안 디스크립터 정보를 전달하는 용도로 사용된다. 만약 이 값이 NULL이면 핸들은 상속되어 질 수 없다.

bManualReset

* TRUE이면 수동 리셋 이벤트, FALSE이면 자동 리셋 이벤트가 생성된다(표 1참고).

bInitialState 

* TRUE이면 신호 상태로, FALSE이면 비 신호 상태로 시작한다.

lpName 

이벤트를 서로 다른 프로세스에 속한 스레드가 사용(공유)할 수 있도록 이름을 줄 수 있습니다. NULL을 사용하면 이름 없는 이벤트가 생성됩니다.

리턴 값

성공

이벤트 핸들

실패

NULL

 

 

이벤트는 다음과 같은 종류가 있다.

 

[표 1] - 이벤트 특성에 따른 종류

종류

특징

자동 리셋 이벤트

* 이벤트를 신호 상태로 바꾸면 기다리는 스레드 중 하나만 깨운 후 자동으로 비신호 상태가 된다. 따라서 자동 리셋 이벤트에 대해서는 ResetEvent() 함수를 사용할 필요가 없다.

수동 리셋 이벤트

* 이벤트를 신호 상태로 바꾸면 계속 신호 상태를 유지하므로 결과적으로 기다리는 스레드를 모두 깨우게 된다. 자동 리셋 이벤트와 달리 이벤트를 비신호 상태로 바꾸려면 명시적으로 ResetEvent 함수를 호출해야 한다.

 

- 다음의 예제는 마스터 스레드가 버퍼에 값을 쓸 동안 다른 스레드들의 사용을 방지하고 있습니다. 첫 째 마스터 스레드는 비신호와 수동 리셋(manual-reset) 이벤트로 이벤트 오브젝트들을 생성해 초기에 비신호 상태를 유지합니다. 그 후 프로그램에서 요구하는 리더 스레드들을 생성하고 마스터 스레드는 쓰기 작업을 실행합니다. 마스터 스레드의 쓰기 작업이 끝나면 이벤트 오브젝트들은 신호상태로 바뀌고 쓰기 작업을 실행합니다. 실행 화면은 그림 5와 같습니다.

아래 쓰이는 모든 함수들은 멀티 스레드 포스팅에서 모두 다루었던 함수들입니다. 만약 이해가 되지 않으시면 다시 한 번 앞의 포스팅들을 참고 하시길 바랍니다.

 

그림 5) 이벤트 예제

 




원본 소스: http://msdn.microsoft.com/en-us/library/ms686915%28v=VS.85%29.aspx

'PROGRAMMING > MFC(C++)' 카테고리의 다른 글

[0421수업] 세마포어  (0) 2011.04.21
[0418수업] ThreadCrash(Critical Section, 뮤텍스)  (0) 2011.04.18
[0415수업] UIThread(Suspend Thread, Resume Thread)  (0) 2011.04.15
[0415수업] Thread  (0) 2011.04.15
[0414수업] WorkerThread  (0) 2011.04.14
Posted by 마마필로 :

1. 단일문서 프로젝트 생성(문서뷰아키텍트 체크 해제)

 

2. MFC클래스 추가(CUIThr), 기본클래스를 CWinThread로 함

 

3. 리소스 뷰-메뉴-IDR_MAINFRAME열어 메뉴추가 할 수 있음

 

 

4. 오른쪽 버튼 클릭하여 ID편집 할 수 있음

 

 

 

5. 마우스 오른쪽버튼 클릭-이벤트 처리기 추가(이 버튼을 클릭했을 때 일어나는 일)

메시지형식(COMMAND), 클래스 목록(CMainFrame)

→ MainFrm.cpp클래스가 추가되고 OnMenuCreateuithread()함수가 생성됨

 

6. 다른 헤더파일들이 인클루드 되어 있는 MainFrm.cpp에 "UIThr.h" 인클루드 해준다.

#include "UIThr.h"

 

7. MainFrm.cpp안의 OnMenuCreateuithread()함수 내에 스레드를 만들어 준다.(코드 작성)

 

void CMainFrame::OnMenuCreateuithread()
{
     AfxBeginThread(RUNTIME_CLASS(CUIThr));
} // 여기까지 하면 스레드가 실행됨


8. UIThr.cpp안의 InitInstance()함수에 ...............을 하는 코드를 작성한다.

BOOL CUIThr::InitInstance()
{
     CMainFrame* pFrame = new CMainFrame;
     if(!pFrame)
      return FALSE;
 

     m_pMainWnd = pFrame;

 

     pFrame->LoadFrame(IDR_MAINFRAME, WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, NULL, NULL);
     pFrame->ShowWindow(SW_SHOW);
     pFrame->UpdateWindow();

     

     return TRUE;
}

// 메인프레임만 가져와서 윈도우의 형태만 띄운 것

 

*CWinThread를 상속받은 CUIThr은 클래스 스스로가 스레드임

 

UIThread.cpp안의 InitInstance()함수와 매우 유사함을 알 수 있다.

실제 UIThread.cpp의 InitInstance()함수도 윈도우를 만들어 주는 것이과

그것과 같은 역할의 코드를 UIthr.cpp내에 구현해 놓은 것이다.

 

9. ChildView.cpp안의 OnPaint()함수 작성

 

void CChildView::OnPaint()
{
     CPaintDC dc(this); // 그리기를 위한 디바이스 컨텍스트
 
     CString strTmp = _T("");
     strTmp.Format(_T("현재 실행되고 있는 스레드의 ID : %d"), ::GetCurrentThreadId());
     dc.TextOut(10, 10, strTmp);
 
     // 그리기 메시지에 대해서는 CWnd::OnPaint()를 호출하지 마십시오.
}

 

10. stdafx.h파일에 아래 메인프레임 헤더파일을 인클루드 한다(인클루드 하는 곳은 꼭 stdafx.h 파일이 아니어도 됨.
#include "MainFrm.h"

 

//여기까지 하면 실행되고 있는 스레드의 ID를 확인할 수 있다. 이것은 실제 윈도우가 아니라 프레임만 가져온 것!

→ 이것이 UI 이다.

 



■ 사용자 UI스레드를 제어하는 방법

 

1. stdafx.cpp에 전역변수 추가

CWinThread* g_pUIThread;

 전역변수를 추가 했을 경우에는 헤더파일에 변수 선언

stdafx.h 파일에

extern CWinThread* g_pUIThread;

 

2. 위의 전역변수를 이용해 중복실행 막기. MainFrm.cpp안의 OnMenuCreateuithread()함수에 코드 작성

 

void CMainFrame::OnMenuCreateuithread()
    {
     

     if(g_pUIThread != NULL)
     {
          AfxMessageBox(_T("사용자 인터페이스 스레드가 이미 실행중입니다."));
          return;
     }

 

     g_pUIThread = AfxBeginThread(RUNTIME_CLASS(CUIThr));
}

 

3. UI를 종료하였음에도 불구하고 이미 실행중이라는 메시지를 막기위해 g_pUIThread의 값을 초기화 해 주어야 한다.

UIThr.cpp의 ExitInstance() 함수에서 g_pUIThread값을 초기화 한다.

 

int CUIThr::ExitInstance()
{
     g_pUIThread = NULL;
     return CWinThread::ExitInstance();
}

스레드를 함수로 만들어서 돌릴수도 있고, 지금과 같이 클래스로 만들어서 돌릴 수도 있다.

각각 어느 경우에 사용하는것인지...

 

클래스로 스레드를 만들어서 사용했을 때(클래스 자체가 스레드 하나가 될 때)의 장점?

역할이 다른 여러개의 함수가 하나의 데이터를 공유하기 위한 충돌을 막을 수 있다.

스레드가 사용할 데이터를 하나의 클래스 안에 넣어서 스레드를 위해서만 공유할 수 있다.(임계영역에서의 경쟁이 없어짐)

 

but 단점(객체를 만들었을 때의 단점)?

스레드의 정지시점을 개별적으로 결정 할 수 없다.

하지만 이것은 스레드를 동시에 실행시키고 내릴 수 있다는 의미에서는 장점이 될 수도 있다.

 


1. 리소스뷰-MENU-IDR_MAINFRAME에서 위와 같은 방법으로 3개의 메뉴 추가

Resume UI Thread, Suspend UI Thread, Exit UI Thread

ID편집하고 이벤트 처리기 추가

 

2. MainFrm.cpp에 생성된 세 개의 함수에 각각 스레드를 재실행 시키는 코드 작성

 

void CMainFrame::OnMenuSuspend()
{
     if (g_pUIThread != NULL)
          g_pUIThread->SuspendThread();
}

 

void CMainFrame::OnMenuResume()
{
     if (g_pUIThread != NULL)
          g_pUIThread->ResumeThread();
}

 

void CMainFrame::OnMenuExit()
{
     if (g_pUIThread != NULL)
          g_pUIThread->PostThreadMessage(WM_QUIT, NULL, NULL);
}

 

이것을 활용하여 사용자 UI가 여러개인 프로그램을 만들 수 있다.


Posted by 마마필로 :

[0415수업] Thread

2011. 4. 15. 01:04 from PROGRAMMING/MFC(C++)

1. 대화상자 기반의 새 프로젝트 생성

2. 버튼 추가, 캡션 수정(Button1 → Run Note Pad)

3. 버튼 더블클릭하여 ThreadDlg.cpp에 이벤트 처러기 생성

4. ThreadDlg.cpp에 노트패드만 스레드로 따로 돌릴 함수 생성

어제의 코드(WorkerThread)에서 복사해와 if(::ShellExecuteEx(&sei)) 문 수정

 

UINT ThreadWaitNotepad(LPVOID pParam)
{
     TCHAR szWinPath[MAX_PATH];

 

     // 윈도우즈 운영체제가 설치된 디렉토리 경로를 알아내는 함수
     ::GetWindowsDirectory(szWinPath, MAX_PATH);
 

     // System 32 폴더의 경로는 GetSystemDirectory() API함수 사용
     lstrcat(szWinPath, TEXT("
\\notepad.exe")); //노트패드의 경로를 가리킴

     SHELLEXECUTEINFO sei;
     ::ZeroMemory(&sei, sizeof(sei));

     sei.cbSize = sizeof(sei);
     sei.hwnd = NULL;
     sei.lpFile = szWinPath;
     sei.nShow = SW_SHOW;
     sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE;
     sei.lpVerb = __TEXT("open");
     sei.lpParameters = NULL;

 

     if(::ShellExecuteEx(&sei))
     {
          HANDLE arhList[2];
          arhList[0]=sei.hProcess;
          arhList[1]=(HANDLE)g_ExitEvent;

  

         DWORD dwResult = ::WaitForMultipleObjects(2, arhList, FALSE, INFINITE);
  

         if(dwResult == WAIT_OBJECT_0)
          {
               AfxMessageBox(TEXT("메모장이 종료되었습니다"));
          }
          else if (dwResult == WAIT_OBJECT_0+1)
          {
               OutputDebugString(TEXT("예제 프로그램이 종료되었습니다"));
          }
     }

     g_pThread = NULL;
     return 0;
}

 

 

선언하지 않은 g_pThread 선언 필요

→ stdafx.cpp파일에

CWinThread* g_pThread = NULL;
CEnent g_ExitEvent;

선언.

매크로가 아닌 전역변수이기 때문에(코드이므로) stdafx.h파일이 아닌 stdafx.cpp에 선언한다

(둘 중에 어디에 넣어도 등록은 됨. 그러나 헤더파일에는 주로 매크로 선언..)

cf.)

변수의 접두사

g_(전역변수)

m_(해당필드의 멤버 변수)

 

stdafx.h에

<afxmt.h> 인클루드.


extern CWinThread* g_pThread;
extern CEvent g_ExitEvent; // 전역변수의 형식지정자 선언

 

WaitForMultipleObjects(): 핸들값을 감시함

(실행시킨 메모장의 종류여부를 감시함→hProcess핸들 감시를 통해서

메모장 자체를 감시하는 것이 아니라 hProcess안에 들어있는 hInstance핸들 값의 변화를 감시하는 것)

 

스레드함수가 하고 있는 일: 메모장이 실행되어 있는지 여부를 계속해서 확인함. (메모장 자체는 스레드가 아님)


 

 

6. ThreadDlg.cpp의 OnBnClickedButton1이벤트 처리기에서 ThreadWaitNotepad()함수 호출

MFC의 스레드 함수 이용(접두사 Afx를 사용하는것은 MFC의 함수)

 

void CThreadDlg::OnBnClickedButton1()

     if (g_pThread != NULL)
     {
          AfxMessageBox(TEXT("메모장 감시 스레드가 이미 실행 중입니다"));
     }
 

     g_pThread = AfxBeginThread(ThreadWaitNotepad, NULL);

 

     if(g_pThread == NULL)
     {
          AfxMessageBox(TEXT("스레드 실행에 실패하였습니다"));
     }
}

 

(보충설명)

AfxBeginThread(함수이름, NULL);

g_pThread에 리턴값을 받음

if(g_pThread == NULL) // 값을 리턴 받은 후에 실행

 

 

⇒ 메모장을 사용중에도 뒤쪽의 Thread프로세스가 정상 작동한다.

 

■ 스레드를 사용해야 하는경우

-어떤 데이터의 변화를 감시해야 할 때

-반복횟수가 너무 많거나 길고 반복횟수를 정확히 알지 못할때

 

■ 스레드를 쓸 때의 좋은점(장점)

일반함수의 경우 정지시키려면 전원차단 혹은 프로세스 강제종료 하지만 데이터(지역변수가 가지고 있던 값들)가 손실되는 위험이 따른다.

하지만 스레드를 사용하면, 외부함수에서 endthread하여 종료시킬 수 있다.(OS가 아니라 application쪽에서 종료할 수 있다)

 


 

스레드 중지

 

Thread의 어플리케이션 클래스에서 ExitInstance()함수 재정의

Thread.cpp에 ExitInstance()함수 코드 작성

 

int CThreadApp::ExitInstance()
{
     g_ExitEvent.SetEvent();
     ::Sleep(10000);

 

     return CWinApp::ExitInstance();
}

 

ExitInstance() : 종료하기 직전에 자동으로 실행되는 함수


Posted by 마마필로 :

1. 대화상자기반의 새 프로젝트 생성(WorkerThread)

 

2. 버튼 한개 추가 하여 이름(Caption)을 'Button1'에서 'Run 메모장 프로그램'으로 변경함

→ 버튼을 클릭하면 메모장이 실해되도록 할것임

 

3. 버튼을 더블클릭하여 WorkerThreadDlg.cpp에 이벤트 프로시저 추가 후 코드 작성

void CWorkerThreadDlg::OnBnClickedButton1()
{
     TCHAR szWinPath[MAX_PATH];

 

     // 윈도우즈 운영체제가 설치된 디렉토리 경로를 알아내는 함수
     ::GetWindowsDirectory(szWinPath, MAX_PATH);
     // System 32 폴더의 경로는 GetSystemDirectory() API함수 사용
     lstrcat(szWinPath, TEXT("\\notepad.exe"));

 

     SHELLEXECUTEINFO sei;
     ::ZeroMemory(&sei, sizeof(sei));

 

     sei.cbSize = sizeof(sei);
     sei.hwnd = NULL;
     sei.lpFile = szWinPath;
     sei.nShow = SW_SHOW;
     sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE;
     sei.lpVerb = __TEXT("open");
     sei.lpParameters = NULL;

 

     if(::ShellExecuteEx(&sei))
     {
          ::WaitForSingleObject(sei.hProcess, INFINITE);
     }
}



MAX_PATH: 따로 선언하지 않았음에도 윈도우 헤더파일에서 기본적으로 제공해주는 매크로 상수이다.


 ::GetWindowsDirectory(szWinPath, MAX_PATH);
 lstrcat(szWinPath, TEXT("
\\notepad.exe"));

//노트패드의 경로를 가리키는 데 쓰임

 

GetWindowsDirectory(): 윈도우즈 운영체제가 설치된 디렉토리 경로를 알아내는 함수

 

sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE;

→ 콘솔에서 명령프롬포트에 인자값을 줘서 실행시키는 방식으로 실행되는 것. 따라서 콘솔도 같이 실행되고 있는 것이다.

 

ShellExecute(): 특정경로에 있는 프로그램을 실행하게 하는 함수.

구조체를 넣어서 실행하려면 ShellExecuteEx()함수를 사용하고 매게변수로 주소를 가리키면 된다.

 

WaitForSingleObject(): Object가 사라질 때까지 계속 waiting하고 있는 함수

OS가 의도적으로 다른 하나의 프로세스를 격리시킨다. 두개의 스레드가 충동하는 것을 막기 위한 것.(데이터동기화 같은 원리)

 

프로그램의 '응답없음' → 메시지 루프문이 더이상 돌지 않고 가만히 있을 때(프로세스가 메시지를 처리하지 못할때)발생

Posted by 마마필로 :

1.대화상자기반 프로젝트 CallConvention 만들기

 

2. 도구상자이용 버튼 3개 추가한 후, 세 개 버튼 각각 속성에서 캡션 수정

Button1 →  __cdecl 방식 호출규약

Button2 → __stdcall 방식의 호출규약

Button3 → __fastcall방식의 호출규약

 

3. 각 버튼을 더블클릭하여 CallConventionDlg.cpp에 메시지 처리 함수 3개를 만들어 준다.

더블클릭하면 자동으로 아래와 같이 코드가 추가됨.

void CCallConventionDlg::OnBnClickedButton1()
{
 // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
}

 

void CCallConventionDlg::OnBnClickedButton2()
{
 // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
}

 

void CCallConventionDlg::OnBnClickedButton3()
{
 // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
}

 

4. 함수 만들고(세 개의 이벤트 처리기 위에 각각 만들어 준다) 호출하기

CallConventionDlg.cpp에 AddValue(), AddValue2(), AddValue3() 세 개의 함수를 만들었음

이벤트 처리기 안에서 각 함수를 호출해 준다.

함수이름 앞에서 호출방식을 결정해 준다.

AddValue는 __cdecl Addvalue, Addvalue2는 __stdcall AddValue2, Addvalue3는 __fastcall AddValue3와 같이.

 

<작성코드>

void __cdecl AddValue(int nA, int nB, int* pnResult)
{
     *pnResult = nA + nB;
}
void CCallConventionDlg::OnBnClickedButton1()
{
     int nResult=0;
     AddValue(10, 20, &nResult);
     // 호출하는 측에서, 메모리 스택에 있는 값 해제를 담당한다
}


int __stdcall AddValue2(int nA, int nB)
{
     // 실제 함수에서 메모리 스택을 해제한 뒤 반환한다
     return nA+nB;
}
void CCallConventionDlg::OnBnClickedButton2()
{
     int nResult=0;
     nResult=AddValue2(10,20);
}

 

int __fastcall AddValue3(int nA, int nB, int nC, int nD, int nE)
{
     return nA+nB+nC+nD+nE;
}
void CCallConventionDlg::OnBnClickedButton3()
{
     int nResult=0;
     nResult=AddValue3(1,2,3,4,5);
}

 

*함수 호출 규약을 붙이지 않고 쓰는 경우는 실제 __cdecl이 생략되어 있는 것이다.

굳이 호출규약을 붙여 주는 이유??

 

호출규약은 호출부가 아닌 실제 구현부에서 결정해 줌.

인자가 2개(3개?)일때 가장 빠름. 기본적으로 2개(3개?)까지 레지스터에 들어가고 나머지는 레지스터 공간에서 대기함

(디버깅하여 디스어셈블리에서 확인 가능. edx, ecx등은 레지스터 공간의 이름)

이와같이 함수의 호출규약을 결정하는 행위를 CallConvention이라 함


'PROGRAMMING > MFC(C++)' 카테고리의 다른 글

[0415수업] Thread  (0) 2011.04.15
[0414수업] WorkerThread  (0) 2011.04.14
[0414수업] SaveGray  (0) 2011.04.14
[스크랩] bitmap 강의 내용 정리  (0) 2011.04.14
[0412수업]  (0) 2011.04.12
Posted by 마마필로 :

■ SaveGray프로젝트를 통해 비트맵을 다루는 방법을 알아봄.

 

1. View클래스에 LBUTTONDOWN, RBUTTONDOWN 메시지 추가

 

2. SaveGrayView.cpp에 코드 추가

inline void RGBtoGray(COLORREF& rgb)
{
       BYTE byGray = (GetRValue(rgb)*30+GetGValue(rgb)*59+GetBValue(rgb)*11)/100

       rgb = RGB(byGray, byGray, byGray);
}

inline형식 지정자의 용도: cpu가 제어권을 넘겨받는 작업을 하지 않음

예를 들어, OnLButtonDown()함수에서 RGBtoGray()함수를 호출하고 사용이 끝난 후에도

cpu에 제어권을 넘기지 않는다.

→ 제어권을 넘겨 주고 받는 작업이 없어지기 때문에 함수의 실행 속도가 굉장히 빨라진다.

그대신, 다른 일반 함수처럼 한 개의 실행코드로 두 개 이상의 호출부와 공유할 수 없다.

다른 곳에서 또 호출할때는 각각의 실행코드가 들어가게 되어(복사의 개념) 내부적으로 실행파일의 크기가 커진다는 단점이 있다.

 

위의 코드에서 반드시 inline 지정자를 사용할 필요는 없지만 대부분 이렇게 사용하는 이유는?

전체 그림 이미지를 흑백으로 바꾸기 위해 많은 연산작업이 필요하기 때문.

위의 코드는 겨우 픽셀하나를 변환하는 것이다.

 

3. Com객체인 C이미지 클래스를 사용하기 위해 stdafx.h에 atlimage.h파일 인클루드 헤야함

#include <atlimage.h>

 

4. SaveGrayView.cpp에 OnLButtonDown()함수 작성

 

void CSaveGrayView::OnLButtonDown(UINT nFlags, CPoint point)
{
      // 바탕화면 크기 및 색상수와 동일한 비트맵 이미지를 만든다.
      CWnd* pWndDesktop = GetDesktopWindow();
      CWindowDC ScrDC(pWndDesktop); // 바탕화면 윈도우 DC
      CClientDC dc(this); // 뷰 윈도우 DC(바탕화면의 비트맵을 가져와 내 뷰에 넣기위함)

      CImage Image;

 

      // 바탕화면의 크기 및 색상수와 동일한 비트맵 이미지를 Image 객체에 만들어 준다.
      Image.Create(300, 300, ScrDC.GetDeviceCaps(BITSPIXEL));

 

     // 이미지DC와 화면DC에 바탕화면 윈도우 DC를 출력한다.
     CDC* pDC = CDC::FromHandle(Image.GetDC());
     pDC->BitBlt(0, 0, 300, 300, &ScrDC, 0, 0, SRCCOPY);

     Image.ReleaseDC();

 

     //Gray 이미지로 변환
     COLORREF rgb;

 

     for (int x=0; x<100; x++)
    {
            for (int y=0; y=100; y++ )
            {
                    rgb = Image.GetPixel(x,y);
                    RGBtoGray(rgb);
                    Image.SetPixel(x, y, rgb);   
            }
    }
    Image.BitBlt(dc.m_hDC, 0, 0);

 

    CView::OnLButtonDown(nFlags, point);
}


Posted by 마마필로 :