'분류 전체보기'에 해당되는 글 174건
- 2008.10.03 [펌] IOCP분석
- 2008.10.03 error LNK2001: unresolved external symbol __imp__....... 1
- 2008.10.03 #pragma
- 2008.10.03 [펌] 스왑
- 2008.10.03 [펌] glibc, uClibc의 차이점
- 2008.10.03 [강의정리] IOCP방식의 Thread Pool
- 2008.10.03 [강의정리] 쓰레드 풀
- 2008.10.03 [강의정리] 로컬변수를 이용한 성능향상2
- 2008.10.03 [강의정리] 로컬변수를 이용한 성능향상1
- 2008.10.03 [강의정리] 4가지 동기화 기법
1. 목적
Winsock2 IOCP를 분석한 것입니다. 이 문서는 크게 두 가지를 설명 하고 있습니다. 전자는 일반적인 IOCP에 대해서 설명합니다. 후자는 IOCP란 무엇이고 장단점은 무엇인지 입니다.
2. IOCP란?
위에서 보는 것처럼 IOCP의 기본적은 구성은 두개의 쓰레드로 만들어 진다. 왼편에 있는 루프는 보통 main() 함수에서 있는 루프고 오른쪽에 있는 루프는 main함수가 루프를 들어가기 전에 생성해야 하는 쓰레드로 보통 Worker쓰레드라고 한다. 권장하는 Worker쓰레드의 수는 하나의 CPU당 두개로(Send/Recv) 되어 있다. 또한 2번에서 3번으로의 데이터 전송은 전역변수를 이용하거나 Worker쓰레드를 생성할 때, 포인터를 전송하여 두 루프가 변수를 공유하는 방법이다. 이에 대해서는 달리 자세히 설명하지는 않겠다.
(1) Wait for client to connect
이 과정에 이전에 이미 IOCP 핸들과 소켓 초기화, 클라이언트의 접속을 대기하는 소켓, 접속한 클라이언트들의 정보를 가지는 배열( 물론, 자료구조는 자신의 선택이다. )을 생성해 두어야 한다.여기는 클라이언트의 접속을 대기하는 소켓에 Accept()를 호출하여 클라이언트가 접속하기를 기다리는 과정이다.
(2) Open communication channel for client
1번 과정에서 클라이언트가 접속을 하면 여기서는 올바른 소켓인지 판단하고, 접속한 소켓을 클라이언트 정보를 저장하는 배열에 저장하고, IOCP에 해당 소켓을 등록하고, 소켓에 읽기 작업을 신청한다. ( 읽기를 먼저 신청하는 이유는 대해서는 설명하지 않겠다. ) 이 때 중요한 것은 현재 WSARecv(), ReadFile()함수를 사용하여 읽기 작업을 신청했지만, IOCP는 작업의 완료 시에 읽기 작업을 끝냈는지 쓰기 작업을 끝냈는지 가르쳐 주지 않는다는 것이다. 따라서, OVERLAPPED구조체를 상속( C관점에서는 새로운 구조체를 선언할 때, OVERLAPPED구조체를 제일 첨에 위치하는 멤버로 선언하여 사용할 수 있다.)하여 읽기 작업을 하는 건지 쓰기 작업을 하는 건지에 대한 흔적을 남겨야 한다.
(3) Read Request from client
이 과정에서 GetQueuedCompletionStatus() 함수를 사용하여 현재 등록된 소켓들 중에 읽기나 쓰기 작업이 완료된 것이 있는지 확인한다. 이 때, IOCP가 알려주는 정보는 2번 과정에서 등록 할 때, 소켓과 같이 입력했던 KEY와 I/O작업을 신청할 때, 인자로 넘겨주었던 OVERLLAPPED 구조체( 확장을 시켰다면 확장 구조체형으로 캐스팅 해주면 된다.)의 주소이다.
(4) Excute request locally
이제 3번 과정에서 받은 key와 OVERLAPPED구조체를 가지고 I/O작업을 신청했을 때, 만약 작업이 끝나면 했었을 작업을 해주면 된다.
(5) Return result to client
이 과정은 사실 4번 과정과 같이 포함되는 부분이다. 즉, 채팅을 예로 들면, 어떤 사용자가 메시지를 입력했을 때, 4번과정의 If( pOverlapped->mode == 읽기 ) {} 블록안이 실행될 것이고, 어떤 메시지인지 확인한 후, 접속한 모든 사용자에게 메시지를 보내게 된다.
3. IOCP 장단점
ü 장점
A. 2번(IOCP)에서 빨간색 문자색으로 써 있는 것 처럼 적은 수의 쓰레드로 윈속2 서버를 구성 할 수 있다. 윈속 2 API중에서 가장 적다.
B. 윈속2 API중에서 CPU 점유율이 가장 낮다. 이 점은 적은 수의 쓰레드를 사용하므로 얻는 이점이다. 쓰레드 숫자가 적으므로 당연히 ThreadContext Switch도 적게 일어난다.
C. 윈속2 API 중에서 가장 확장성과 성능이 뛰어나다.
ü 단점
A. 프로그램이 어렵다. 즉, 개념 파악이 어렵다.
B. 플랫폼이 제약이 있다. 윈속2이상과 윈도우 NT이상만 지원한다. 정확하게는 윈도우 2000이상에서 개발 하는게 낫다.
- www.codeproject.com, msdn.Microsoft.com, www.codeguru.com
- PlatformSDK, Network Programming For Microsoft Windows
출처 : http://blog.naver.com/process3/20022353267
error LNK2001: unresolved external symbol __imp__.......

#pragma 는 #로 시작하는 전처리구문 지시자 중 컴파일러에 종속적인 명령으로,
컴파일러에 특정한 옵션 명령을 내리기 위해 사용하는 데요,
컴파일러에 종속적이기 때문에 컴파일러를 변경했을 경우 실행을 보장하지 못합니다.
대표적인 #pragma 명령 중에서 once나 pack, comment(lib, *) 등이 있는데 이는 각각
컴파일러에
1 once : 해당 소스가 단 한번만 포함되게 한다(여러 번 인클루드 되는 것을
컴파일러 차원에서 막아줍니다)
2 pack : 변수 정렬을 인위적으로 변경시킨다(보통은 4바이트로 지정되어 있습니다)
3 comment(lib, *) : comment로 사용할 수 있는 명령은 여러 개 있는데,
그중 가장 대표적인 것이 lib 으로, 해당 라이브러리를 링크시켜준다
과 같은 명령을 내립니다.
api나 mfc의 구분과 관계없이 해당 컴파일러에서 사용하는 명령이랍니다
MSDN에 있는 내용
code_seg
#pragma code_seg( ["section-name"[,"section-class"] ] )
Specifies a code section where functions are to be allocated. The code_seg pragma specifies the default section for functions. You can, optionally, specify the class as well as the section name. Using #pragma code_seg without a section-name string resets allocation to whatever it was when compilation began.
==========================================================
실행파일에는 코드영역과 데이터영역이 구분되어 있다는 것은 아시지요.
┌───────┐
│ │
│ │
│ 데이터영역 │
│ │
│ │
├───────┤
│ │
│ │
│ 코드 영역 │
│ │
│ │
└───────┘
이런 식의 그림은 아마 어디선가 많이 보셨을겁니다.(그림이 깨지네요.편집할 땐 제대로 보였는데...)
이런 영역을 section이라고 하지요. 위의 설명에 나오는 section이라는 용어가 이것입니다.
실제로 데이터 영역과 코드 영역외에도 exe나 dll에는 여러 section을 포함할 수 있습니다.
이건 dumpbin이라는 툴로 살펴볼 수 있습니다.
제 컴(Windows XP)에서 dumpbin c:\windows\notepadd.exe 를 해 보았더니
2000 .data
2000 .rsrc
7000 .text
이렇게 나오는 군요.
여기서 .data에는 초기화된 데이터가 .rsrc에는 리소스들이, .text에 코드가 들어갑니다.
이러한 .data, .rsrc, .text 등은 일반적으로 정해져 있는 것들입니다.
위 MSDN의 설명에 있는 section-name이라는 것이 바로 .data, .rsrc, .text 등을 뜻하는 겁니다.
즉, #pragma code_seg( .data ) 처럼 사용한다는 거지요.
그리고 Specifies a code section where functions are to be allocated.라는 설명은
Specifies a section where functions or data or etc. are to be allocated. 이렇게 이해하면 더 나을 듯 하네요.
그런데 이런 정해진 이름말고도 사용자가 새로운 영역을 정할 수 있습니다.
#pragma data_seg("Shared")
DWORD g_dwThreadIdPMRestore = 0;
HWND g_hwnd = NULL;
#pragma data_seg()
이런 식으로 하면 g_dwThreadIdPMRestored와 g_hwnd가 디폴트 데이터 섹션인 .data에 배치되지 않고,
Shared라는 이름으로 만들어진 섹션에 배치되는 것입니다.
=====================================================
#pragma data_seg(...)가 사용되는 곳은
dll간의 데이터 공유를 위해 사용될 수 있습니다.
그리고 그 외에 어디서 사용되는지는 저도 아직 본 적이 없어서 잘 모르겠습니다.
======================================================
참고로 위의 내용은 "어드밴스 윈도우 NT"를 참고해서 작성했습니다.
이 책은 지금 나오지 않고 이 책의 새 버전이 "Programming Applications for Microsoft Windows"(Jeffrey Richer 저)로
번역판도 나와 있습니다.
주기억장치에서 어떤 작업을 실행하고 있는 동안 그 작업보다 순위가 높은 작업수행이 필요하여
끼어들기 형태로 일을 해야 할 때 이미 실행 중인 프로그램과 데이터를
일시적으로 보조기억장치에 옮기고(swap out), 끼어들기가 끝난 후에 보조기억장치에서
주기억장치로 다시 중단된 작업을 탑재한다(swap in).
이처럼 주기억장치에서 보조기억장치로 프로그램이나 데이터 등을 교환하는 것을 교환이라 한다.
우선 순위를 지닌 작업의 처리를 위한 충분한
기억 장소가 없는 경우에는,
하나 이상의 작업이 주기억장치에서 제거되고 보조기억장치로 교환되어야 한다.
GNU C Library
http://www.gnu.org/software/libc/libc.html
GLIBC 는 GNU C Library 를 뜻합니다.
C언어 자체는 input/ouput , 메모리관리, 문자열 조작 등의 기능이 없습니다.
stdio.h 헤더 파일을 include 해야
printf 함수를 가지고 출력을 하고
scanf 함수를 가지고 입력을 받을 수 있습니다.
malloc , free 함수가 있어야 메모리 관리를 하고
strcmp, strcat 등의 함수가 있어야 문자열을 조작할 수가 있습니다.
이러한 것들을 할 수 있도록 도와주는 C library 중에
GNU 에서 제공하는 GLIBC 가 있는 것 입니다.
uClibc 는 작업환경이 일반 PC 보다 비교적 협소한 임베디드 환경에서 사용할 수 있도록
GLIBC 를 축소한 형태의 library 입니다.
[ 소스 ]
#include "StdAfx.h"
#include
#include "IOCPThreadPool.h"
CIOCPThreadPool::CIOCPThreadPool(void)
{
m_hStopEvent = NULL;
m_hCompletionPort = NULL;
int i = 0;
for( i = 0 ; i < WORKERTHREAD_COUNT ; i++ )
m_hWorkerThread[i] = NULL;
}
CIOCPThreadPool::~CIOCPThreadPool(void)
{
CloseHandle(m_hCompletionPort);
CloseHandle(m_hStopEvent);
}
DWORD CIOCPThreadPool::Create(int nSize)
{
// Worker thread 생성
unsigned int threadID;
int i;
// I/O Completion Port 생성
m_hCompletionPort = ::CreateIoCompletionPort( INVALID_HANDLE_VALUE, NULL, 0, 0 );
if( INVALID_HANDLE_VALUE == m_hCompletionPort )
return ERROR_CAN_NOT_COMPLETE;
for( i = 0 ; i < WORKERTHREAD_COUNT ; i++ )
{
m_hWorkerThread[i] = (HANDLE)_beginthreadex( 0, 0, WorkerThread, this, 0, &threadID );
}
m_hStopEvent = ::CreateEvent( 0, TRUE, FALSE, 0 );
return 0;
}
DWORD CIOCPThreadPool::InsertQueueItem(DWORD lpData)
{
PostQueuedCompletionStatus(m_hCompletionPort, 0, (DWORD_PTR)lpData, NULL);
return 0;
}
unsigned int __stdcall CIOCPThreadPool::WorkerThread(LPVOID lpData)
{
LPVOID pWorkItem = NULL;
LPOVERLAPPED lpOverlapped = NULL;
DWORD numberOfBytes = 0;
DWORD dwData = 0;
DWORD dwKey = 0;
DWORD dwCount = 0;
CIOCPThreadPool* pThis = (CIOCPThreadPool*)lpData;
while (1)
{
if (WAIT_OBJECT_0 == WaitForSingleObject(pThis->m_hStopEvent, 0))
{
break;
}
BOOL bSuccess = GetQueuedCompletionStatus(pThis->m_hCompletionPort, &numberOfBytes, &dwKey, &lpOverlapped, INFINITE);
//AfxTrace(_T("GetQueuedCompletionStatus\n"));
//
// Process the work item if there is one.
//
if( -1 == dwKey )
{
break;
}
else
{
//Do it...
dwData = dwKey;
dwCount++;
}
}
AfxTrace(TEXT("[%d] Semaphore Use Thread Pool Data|Count[%d, %d] \r\n"), GetCurrentThreadId(), dwData, dwCount);
return 0;
}
DWORD CIOCPThreadPool::Stop(void)
{
int i = 0;
DWORD dwKey = -1;
SetEvent(m_hStopEvent);
for( i = 0 ; i < WORKERTHREAD_COUNT ; i++ )
InsertQueueItem(dwKey);
WaitForMultipleObjects(WORKERTHREAD_COUNT, m_hWorkerThread , TRUE, INFINITE);
return 0;
}
-------------------------------------------------------------------------------------
=> 앞서 Thread Pool에 대해 간단하게 설명했다....IOCP에서의 쓰레드 풀은
CreateIoCompletionPort라는 API를 이용하여 운영체제에 IO Port를 쓰겠다는 것을 통보한다.
=> 앞의 쓰레드 풀과 전체적인 의미는 차이가 없다. 다만 앞에는 직접 사용자가 데이터를
넣고 빼고..동기화도 직접해주는 것이지만 여기서는 OS가 전부 처리해준다는 점만 다르다.
( 위의 그림 부분을 OS가 처리 )
ps. USN 2차에서는 쓰레드풀을 위한 4개의 쓰레드, Read, Write를 위한 2개 쓰레드, 데이터
처리를 위한 쓰레드1개를 써서 총 7개의 쓰레드가 쓰였다.
=> 앞의 세미포어 대신에 여기서는 IOCP가 대신해주면서 처리한다.
=> 두 방법의 성능차이는 IOCP를 이용한 것이 실제로 세미포어를 이용하여 작성한거 보다
빠르다.
=> windows2000이상에서는 QueueUserWorker라는 것이 있다. 이는 밑의 그림 처럼 쓰레드 풀
까지 관리해주는 역활을 한다.
=> 필요성
- 네트워크 프로그래밍에서 사용자가 늘어날 때마다 쓰레드를 만들 수는 없다.
어떤 프로그램을 만들 때 몇개의 쓰레드를 만들 것인가? 에 대한 설계가 필요
[소스]
#include "StdAfx.h"
#include "ThreadQueuePool.h"
#include
CThreadQueuePool::CThreadQueuePool(void)
{
m_hQueueSemaphore = NULL;
m_hStopEvent = NULL;
m_phThreads = NULL;
m_dwSize = 0;
InitializeCriticalSection(&CS);
}
CThreadQueuePool::~CThreadQueuePool(void)
{
if (NULL != m_phThreads)
{
delete m_phThreads;
m_phThreads = NULL;
}
DeleteCriticalSection(&CS);
}
DWORD CThreadQueuePool::Create(int nSize)
{
if (0 >= nSize)
{
return ERROR_INVALID_PARAMETER;
}
DWORD dwRet = ERROR_SUCCESS;
m_dwSize = nSize;
//lInitialCount Value is 0
m_hQueueSemaphore = CreateSemaphore(NULL, 0, LONG_MAX, NULL);
if (NULL == m_hQueueSemaphore)
{
dwRet = ERROR_CAN_NOT_COMPLETE;
goto QUIT;
}
//bManualReset Value is TRUE
m_hStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL );
if (NULL == m_hStopEvent)
{
dwRet = ERROR_CAN_NOT_COMPLETE;
goto QUIT;
}
//Allocate Thread Handle
m_phThreads = new HANDLE[nSize];
for (int i = 0; i < nSize; i++)
{
// create thread
UINT uiThreadId = 0;
m_phThreads[i] = (HANDLE)_beginthreadex(NULL,
0,
CThreadQueuePool::ThreadFunc,
this,
0,
&uiThreadId);
if (NULL == m_phThreads[i])
{
dwRet = ERROR_CAN_NOT_COMPLETE;
m_dwSize = i;
StopThread();
goto QUIT;
}
}
QUIT:
if (ERROR_SUCCESS != dwRet)
{
if (NULL != m_hQueueSemaphore)
{
CloseHandle(m_hQueueSemaphore);
m_hQueueSemaphore = NULL;
}
if (NULL != m_hStopEvent)
{
CloseHandle(m_hStopEvent);
m_hStopEvent = NULL;
}
}
return dwRet;
}
DWORD CThreadQueuePool::InsertItem(DWORD dwData)
{
DWORD dwRet = ERROR_SUCCESS;
{
EnterCriticalSection(&CS);
m_aryItemQueue.Add(dwData);
LeaveCriticalSection(&CS);
}
if (FALSE == ReleaseSemaphore(m_hQueueSemaphore, 1, NULL))
{
dwRet = ERROR_CAN_NOT_COMPLETE;
ASSERT(FALSE);
}
return dwRet;
}
DWORD CThreadQueuePool::StopThread(void)
{
if (NULL == m_phThreads)
return ERROR_SUCCESS;
DWORD dwReturn = ERROR_SUCCESS;
SetEvent(m_hStopEvent);
dwReturn = WaitForMultipleObjects(m_dwSize, m_phThreads, TRUE, INFINITE);
if (WAIT_OBJECT_0 == dwReturn)
{
AfxTrace(TEXT("All Thread terminate \r\n"));
delete m_phThreads;
m_phThreads = NULL;
}
else
{
ASSERT(FALSE);
}
return dwReturn;
}
unsigned int __stdcall CThreadQueuePool::ThreadFunc(void* pParam)
{
//You must check pParam Value...
HANDLE hObj[INDEX_END] = {0,};
DWORD dwResult = 0;
CThreadQueuePool* pThis = (CThreadQueuePool*)pParam;
BOOL bGo = TRUE;
LPVOID lpWorkItem = NULL;
DWORD dwRet = 0;
DWORD dwCount = 0;
hObj[INDEX_QUEUE] = pThis->m_hQueueSemaphore;
hObj[INDEX_STOP] = pThis->m_hStopEvent;
while(bGo)
{
//This Thread Wait Two Handle
//bWaitAll value is FALSE
//dwMilliseconds(WaitTime) value is INFINITE
dwResult = WaitForMultipleObjects(INDEX_END, hObj, FALSE, INFINITE);
switch(dwResult)
{
// hObj[INDEX_QUEUE] was signaled.
case WAIT_OBJECT_0 + INDEX_QUEUE:
//AfxTrace(TEXT("[%d] Semaphore signaled and then Thread Excute \r\n"), GetCurrentThreadId());
dwCount++;
{
EnterCriticalSection(&pThis->CS);
dwRet = pThis->GetWorkItem();
LeaveCriticalSection(&pThis->CS);
}
//Doing WorkItem
//Sleep(1000);
break;
// hObj[INDEX_STOP] was signaled.
case WAIT_OBJECT_0 + INDEX_STOP:
bGo = FALSE;
break;
default:
ASSERT(FALSE);
break;
}
}
AfxTrace(TEXT("[%d] Semaphore Use Thread Pool Data, Count[%d, %d] \r\n"), GetCurrentThreadId(), dwRet, dwCount);
return 1;
}
DWORD CThreadQueuePool::GetWorkItem(void)
{
DWORD dwRet = 0;
if (0 < m_aryItemQueue.GetSize())
{
//Synchronization
dwRet = m_aryItemQueue[0];
m_aryItemQueue.RemoveAt(0);
}
return dwRet;
}
-------------------------------------------------------------------------------------
=> 세마포어를 이용하여 Queue방식으로 쓴다. 세마포어 개념 또한 Count개념이므로...
=> 동기화는 Critical Section으로써 데이터를 넣고 빼고한다.
=> LONG_MAX(소스 참조)만큼 세마포어를 만든다.
=> Insert부분에서 Queue에 Critical Section으로 동기화하고 다른 쓰레드에 쓰라고 통보(ReleaseSemaphore)해준다.
=> 생성한 5개의 쓰레드중에 하나가 꺠어나서 Queue의 데이터를 꺼내서 처리한다.
출처 : 신경준씨 사외교육 http://blog.naver.com/process3/20052147545
ex) 메일서버
- 메일서버를 크게 두개로 나누면 SMTP서버와 처리하고 다시 던져주는 서버로 나눌 수 있는데
메일서버 전체영역에 동기화영역을 넣지 말고 처리할 데이터를 잠깐 복사하여 지역변수상에
서 처리하면 속도를 높일 수 있다. 밑의 두 쓰레드상에서의 예제를 보자
[ 초기 싱글 쓰레드에서의 동작 소스 ]
// SingleThread_Produce_Consume.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <windows.H>
#include <stdio.H>
#include <tchar.H>
#define LOOP_CNT 500000
void Produce(int &nData)
{
nData++;
Sleep(0);
}
void Consume(int &nData)
{
nData--;
Sleep(0);
}
int _tmain(int argc, _TCHAR* argv[])
{
BOOL bRun = TRUE;
int nData = 0;
int nCnt = 0;
DWORD startTime = GetTickCount();
while (TRUE == bRun )
{
Produce(nData);
Consume(nData);
if( LOOP_CNT == nCnt)
{
bRun = FALSE;
}
nCnt++;
}
_tprintf(_T("%d %d\n"), GetTickCount() - startTime, nData);
return 0;
}
[ 동기화 되지 않은 두 쓰레드 소스 ]
#include "stdafx.h"
#include
#include
#include
int nData = 0;
#define LOOP_CNT 500000
void Produce(int &nData)
{
nData++;
Sleep(0);
}
void Consume(int &nData)
{
nData--;
Sleep(0);
}
DWORD CALLBACK ProduceThread(void* arg)
{
int nCnt = 0;
BOOL bRun = TRUE;
while(TRUE == bRun)
{
Produce(nData);
nCnt++;
if( LOOP_CNT == nCnt)
{
bRun = FALSE;
}
}
_tprintf(_T("ProduceThread %d\n"), nCnt);
return 0;
}
DWORD CALLBACK ConsumeThread(void* arg)
{
int nCnt = 0;
BOOL bRun = TRUE;
while(TRUE == bRun)
{
Consume(nData);
nCnt++;
if( LOOP_CNT == nCnt)
{
bRun = FALSE;
}
}
_tprintf(_T("ConsumeThread %d\n"), nCnt);
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hThread[2] = {NULL};
DWORD startTime = GetTickCount();
hThread[0] = CreateThread(NULL, 0, ProduceThread, (LPVOID)0, 0, NULL);
hThread[1] = CreateThread(NULL, 0, ConsumeThread, (LPVOID)0, 0, NULL);
WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
_tprintf(_T("%d %d\n"), GetTickCount() - startTime, nData);
return 0;
}
[ 쓰레드 함수내의 전체영역의 동기화 ]
#include "stdafx.h"
#include
#include
#include
#define LOOP_CNT 500000
int nData = 0;
CRITICAL_SECTION CriticalSection;
void Produce(int &nData)
{
nData++;
Sleep(0);
}
void Consume(int &nData)
{
nData--;
Sleep(0);
}
DWORD CALLBACK ProduceThread(void* arg)
{
int nCnt = 0;
BOOL bRun = TRUE;
while(TRUE == bRun)
{
EnterCriticalSection(&CriticalSection);
Produce(nData);
nCnt++;
if( LOOP_CNT == nCnt)
{
bRun = FALSE;
}
LeaveCriticalSection(&CriticalSection);
}
_tprintf(_T("ProduceThread %d\n"), nCnt);
return 0;
}
DWORD CALLBACK ConsumeThread(void* arg)
{
int nCnt = 0;
BOOL bRun = TRUE;
while(TRUE == bRun)
{
EnterCriticalSection(&CriticalSection);
Consume(nData);
nCnt++;
if( LOOP_CNT == nCnt)
{
bRun = FALSE;
}
LeaveCriticalSection(&CriticalSection);
}
_tprintf(_T("ConsumeThread %d\n"), nCnt);
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hThread[2] = {NULL};
InitializeCriticalSection(&CriticalSection);
DWORD startTime = GetTickCount();
hThread[0] = CreateThread(NULL, 0, ProduceThread, (LPVOID)0, 0, NULL);
hThread[1] = CreateThread(NULL, 0, ConsumeThread, (LPVOID)0, 0, NULL);
WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
_tprintf(_T("%d %d\n"), GetTickCount() - startTime, nData);
DeleteCriticalSection(&CriticalSection);
return 0;
}
[ Temp값을 이용하여 복사한 뒤 로컬변수상에서의 동기화 ]
#include "stdafx.h"
#include
#include
#include
#define LOOP_CNT 500000
int nData = 0;
CRITICAL_SECTION CriticalSection;
void Produce(int &nData)
{
nData++;
Sleep(0);
}
void Consume(int &nData)
{
nData--;
Sleep(0);
}
DWORD CALLBACK ProduceThread(void* arg)
{
int nCnt = 0;
BOOL bRun = TRUE;
int nTemp = 0;
while(TRUE == bRun)
{
EnterCriticalSection(&CriticalSection);
nTemp = nData;
LeaveCriticalSection(&CriticalSection);
Produce(nTemp);
nCnt++;
if( LOOP_CNT == nCnt)
{
bRun = FALSE;
}
}
_tprintf(_T("ProduceThread %d\n"), nCnt);
return 0;
}
DWORD CALLBACK ConsumeThread(void* arg)
{
int nCnt = 0;
BOOL bRun = TRUE;
int nTemp = 0;
while(TRUE == bRun)
{
EnterCriticalSection(&CriticalSection);
nTemp = nData;
LeaveCriticalSection(&CriticalSection);
Consume(nTemp);
nCnt++;
if( LOOP_CNT == nCnt)
{
bRun = FALSE;
}
}
_tprintf(_T("ConsumeThread %d\n"), nCnt);
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hThread[2] = {NULL};
InitializeCriticalSection(&CriticalSection);
DWORD startTime = GetTickCount();
hThread[0] = CreateThread(NULL, 0, ProduceThread, (LPVOID)0, 0, NULL);
hThread[1] = CreateThread(NULL, 0, ConsumeThread, (LPVOID)0, 0, NULL);
WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
_tprintf(_T("%d %d\n"), GetTickCount() - startTime, nData);
DeleteCriticalSection(&CriticalSection);
return 0;
}
[ 전역변수를 이용 ]
#include "stdafx.h"
#include <windows.H>
#include <stdio.H>
#include <tchar.H>
volatile int data1;
volatile int data2;
DWORD CALLBACK TestThread1(void* arg)
{
for (int i = 0; i < 500000000; ++i)
data1 = data1 + 1;
return data1;
}
DWORD CALLBACK TestThread2(void* arg)
{
for (int i = 0; i < 500000000; ++i)
data2 = data2 + 1;
return data2;
}
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE thread[2];
SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
DWORD startTime = GetTickCount();
thread[0] = CreateThread(NULL, 0, TestThread1, (LPVOID)0, 0, NULL);
thread[1] = CreateThread(NULL, 0, TestThread2, (LPVOID)0, 0, NULL);
WaitForMultipleObjects(2, thread, TRUE, INFINITE);
_tprintf(_T("%d\n"), GetTickCount() - startTime);
return 0;
}
[ 지역변수를 이용 ]
#include "stdafx.h"
#include <windows.H>
#include <stdio.H>
#include <tchar.H>
//volatile int data1;
//volatile int data2;
DWORD CALLBACK TestThread1(void* arg)
{
int data1;
for (int i = 0; i < 500000000; ++i)
data1 = data1 + 1;
return data1;
}
DWORD CALLBACK TestThread2(void* arg)
{
int data2;
for (int i = 0; i < 500000000; ++i)
data2 = data2 + 1;
return data2;
}
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE thread[2];
SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
DWORD startTime = GetTickCount();
thread[0] = CreateThread(NULL, 0, TestThread1, (LPVOID)0, 0, NULL);
thread[1] = CreateThread(NULL, 0, TestThread2, (LPVOID)0, 0, NULL);
WaitForMultipleObjects(2, thread, TRUE, INFINITE);
_tprintf(_T("%d\n"), GetTickCount() - startTime);
return 0;
}
출처 : 신경준씨 사외교육 http://blog.naver.com/process3/20052147545
-> Critical section이 Mutex보다 속도가 10배정도 빠르다.
Critical section는 유저모드에서 작동하는 obj이기 때문이며 같은 프로세스안에서만 사용이
가능하다.
- 두개의 Critical section을 돌려도 Mutex보다 빠르다. Critical section은 Count만을 가지고
있으며 Mutex는 커널까지 내려가서 Critical영역에 대한 관리를 요청하기 때문에 느릴 수 밖에
없다.
- 반면에 다른 프로세스에서 다른 프로세스의 자원을 사용할 때는 Mutex를 사용할 수 밖에 없다.
- 정리하자면 Critical section은 같은 프로세스내에서의 임계영역(Critical 영역)을 관리 한다.
(위의 머리말 의미와 동일)
-> 코드2듀오 일때 spinCount를 쓰는 것이 5%정도 더 성능이 올라간다.
-> Critical section내에는 sleep를 쓰지 않는 것을 기본으로 하는데 그대신 이벤트처리를
해도 된다.
[ 세마포어 ]
-> 예제 소스
#include "stdafx.h"
#include
#include "process.h"
#define THREAD_NUM 10
#define INIT_COUNT 0
#define TOTAL_COUNT 10
#define RELEASE_COUNT 1
int g_nIndex = 0;
typedef struct msg_block_tag
{
HANDLE hSemaphore;
} THREAD_ARG;
void PrintTrace()
{
_tprintf(_T("Thread Exit ID = %d|%0x \n"), g_nIndex, GetThreadId(GetCurrentThread()));
g_nIndex++;
}
unsigned int WINAPI TestThread(LPVOID lpThreadData);
int _tmain(int argc, _TCHAR* argv[])
{
unsigned int unThreadId = 0;
THREAD_ARG stThreadArg;
int i = 0;
HANDLE hThread[THREAD_NUM] = {NULL};
LONG lPreviousCount = 0;
stThreadArg.hSemaphore = CreateSemaphore(NULL, INIT_COUNT, TOTAL_COUNT, NULL);
for( i = 0; i < THREAD_NUM; i++)
{
hThread[i] = (HANDLE)_beginthreadex(NULL, 0, TestThread, &stThreadArg, 0, &unThreadId);
}
ReleaseSemaphore(stThreadArg.hSemaphore, RELEASE_COUNT, &lPreviousCount );
WaitForMultipleObjects(THREAD_NUM, hThread, TRUE, INFINITE);
for( i = 0; i < THREAD_NUM; i++)
{
CloseHandle(hThread[i]);
hThread[i] = NULL;
}
CloseHandle(stThreadArg.hSemaphore);
stThreadArg.hSemaphore = NULL;
return 0;
}
unsigned int WINAPI TestThread(LPVOID lpThreadData)
{
THREAD_ARG *lpArg = (THREAD_ARG*)lpThreadData;
DWORD dwThreadStatus = 0;
LONG lPreviousCount = 0;
while(TRUE)
{
dwThreadStatus = WaitForSingleObject(lpArg->hSemaphore, INFINITE);
}
}
return 0;
}
-> 세마포어의 큰 장점은 숫자를 이용할 수 있어 자기가 원하는 쓰레드 수를 고를 수 있다는 점이다.
- 소스를 보면 10개의 쓰레드가 모두 wait상태에서 있다가 하나가 벗어나면
ReleaseSemaphore를 하여 다음 쓰레드에게 알려주게 된다.
- 세마포어의 ReleaseCount를 1로 하면 Mutex와 똑같이 작동을 한다.
- 웹 서버개발시 Mutex와 세마포어를 같이 사용할 수도 있다.
ex) 80개의 쓰레드가 있을 때 Mutex를 쓰면 1개씩 작동을 하므로 나머지 79개가 서로 경쟁
을 하게 되어 비효율적이다. 그러므로 CPU가 8개라면 세마포어를 이용하여 ReleaseCount
를 8개로 주고 Mutex를 이용하여 그 8개끼리 경쟁을 하게 하면 더욱더 효율적으로
구현할 수 있다.( 강의 PPT 15번 참조 )
- 강의 PPT 15번 내용
----------------------------------------------------------------------------------------
Semaphore Throttles (Thundering Herd(놀란 양떼))
하나의 동기화 객체(크리티컬 섹션 또는 뮤텍스)를 기다리는 스레드가
너무 많은 경우, 세마포어를 하나 더 둬서, 해당 동기화 객체를 기다리는
스레드의 숫자를 줄여주는 것이 성능 향상에 도움이 된다.
while (TRUE) { // Worker loop
WaitForSingleObject (hThrottleSem, INFINITE);
WaitForSingleObject (hMutex, INFINITE);
... Synchronization code ...
ReleaseMutex (hMutex);
ReleaseSemaphore (hThrottleSem, 1, NULL);
} // End of worker loop
----------------------------------------------------------------------------------------