본문 바로가기
TCP/IP/윈도우기반

쓰레드동기화

by 꿀꿀이냐옹이 2009. 10. 8.
반응형

1.쓰레드 동기화 기법

(1)쓰레드 동기화 기법의 분류

<1>프로세스 실행의 두 가지 모드

-유저모드와 커널모드가 존재한다.이렇게 모드를 나눈 이유는 시스템의 안정성을 보장하기 위해서 이다.

 

<2>유저모드

-일반적인 프로그램의 실행모드다.

-시스템리소스접근이 허용되지 않는다.

-유저모드 동기화 기법 : CRITICAL_SECTION오브젝트를 사용한다.

 

<3>커널모드

-시스템 리소스를 접근을 위한 프로세스의 실행모드,

-커널 오브젝트를 통한 시스템 리소스로의 접근시 유저모드에서 커널모드로의 변환이 발생한다.

-커널 모드 동기화 기법 : Event , Semaphore , Mutex커널 오브젝트를 사용한다.

-리소스접근이 허용되긴 하지만 운영체제에 의해서 접근이 되는 것이다.운영체제에 의해서 리소스를 안정적으로 운영할 수 있다.

 

(2)유저모드와 커널모드의 장단점

 [유저모드의 장단점]

-프로그래밍하기 수월하다.

-유저모드로 실행하면 커널모드로의 전환이 필요없기 때문에 프로그램의 실행속도가 빠르다.

-유저모드는 커널모드 동기화기법에 비해서 제한된 기능만 지닌다.

 

[커널모드의 장단점]

-커널모드는 모드의 전환이 발생한다.(유저모드=>커널모드 , 접근종료후 다시 커널모드=>유저모드로 전환해야한다

-Deadlock문제를 막을 수 있다.(단 ,해결책이 되는 것이 아니다)

-둘 이상의 프로세스 내에 존재하는 쓰레드 간의 동기화가 가능하다.

(시스템리소스는 커널이 관리하므로 커널오브젝트를 참조해서 동기화가 가능하다)

-실행속도의 저하가 발생한다.

 

(3)CRITICAL_SECTION를 이용한 동기화

 

<1>동기화 관련함수

void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

 

 

<2>예제

 

/*
 * cs_sync.c
 * Written by SW. YOON
 */

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <process.h>    /* _beginthreadex, _endthreadex */

DWORD WINAPI ThreadSummation(void *arg);
void ErrorHandling(char *message);

int sum=0;
int sum1[]={1, 5};
int sum2[]={6, 10};

CRITICAL_SECTION cs;

int main(int argc, char **argv)
{
 HANDLE hThread1, hThread2;
 DWORD dwThreadID1, dwThreadID2;

 InitializeCriticalSection(&cs);

    hThread1 = (HANDLE)_beginthreadex(NULL, 0, ThreadSummation, (void*)sum1, 0, (unsigned *)&dwThreadID1);
 hThread2 = (HANDLE)_beginthreadex(NULL, 0, ThreadSummation, (void*)sum2, 0, (unsigned *)&dwThreadID2);
    if(hThread1==0 || hThread2==0)
  ErrorHandling("쓰레드 생성 오류");

 if(WaitForSingleObject(hThread1, INFINITE)==WAIT_FAILED)
  ErrorHandling("쓰레드 wait 오류");
  
 if(WaitForSingleObject(hThread2, INFINITE)==WAIT_FAILED)
  ErrorHandling("쓰레드 wait 오류");

 printf("main함수 종료, sum = %d \n", sum);
 DeleteCriticalSection(&cs);

    return 0;
}

DWORD WINAPI ThreadSummation(void *arg)
{
  int start=((int*)arg)[0];
  int end=((int*)arg)[1];

  for( ; start<=end; start++) {
   EnterCriticalSection(&cs);
   sum+=start;
   LeaveCriticalSection(&cs);
  }
  return 0;
}

void ErrorHandling(char *message)
{
  fputs(message, stderr);
  fputc('\n', stderr);
  exit(1);
}

2.커널오브젝트 동기화(커널모드 동기화)

 

(1)커널오브젝트의 상태

<1>Mutex 기준으로

 시그널드 상태 ------------------>소유가 가능한 상태

 넌시그널드 상태 ---------------->이미 소유되어진 상태    -----------------------대기상태

 

<2>Semaphore기준으로

WaitForSingleObject호출하면 카운터가 하나 감소한다.

시그널드 상태 ------------------->세마포어 카운트가 0이 아닌 경우

넌시그널드 상태------------------>세마포어 카운트가 0인 경우 ------------------대기상태

 

 

(2)뮤텍스에 의한 동기화 기법

 

HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes,

                                BOOL bInitalOwner ,                           //false 이면 signaled,즉 누구에게 소유되지 않은 상태

                                LPCTSTR lpName);                            //이름

 

BOOL ReleaseMutex(HANDLE hMutex) ; //nonsignaled ->signaled로 바꾼다.

(상태변경시켜준다)

 

BOOL CloseHandle(HANDLE hObject) ;

 

뮤텍스 생성(false 전달로 시그널상태로 생성)

A쓰레드가 WaitForsingleObject호출하면서 뮤텍스가 시그널 상태이므로 임계영역으로 들어간다. 

A쓰레드가 뮤텍스를 소유하게되는 것이다.

WaitForSingleObject가 리턴되면서 넌시그널상태,즉 이미 소유된 상태가 된다.

A쓰레드는 작업완료후 ReleaseMutex를 호출하여 시그널 상태로 변경한다.즉 뮤텍스는 소유주가 없는 상태가 된다.

 

WaitForSingleObject에 의해서 대기상태에 있던 B쓰레드는 뮤텍스가 시그널상태가 되자 리턴되면서

넌시그널 상태 즉 B쓰레드에 의해서 소유된 상태로 바뀌게 되고 임계영역으로 들어가 작업을 할 수 있게 된다.

 

 

(3)뮤텍스에 의한 동기화 기법의 예제

 

/*
 * mutex_win.c
 * Written by SW. YOON
 */

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <process.h>    /* _beginthreadex, _endthreadex */

DWORD WINAPI ThreadIncrement(void *arg);
void ErrorHandling(char *message);

char thread1[] = "A Thread";
char thread2[] = "B Thread";

int number=0;
HANDLE hMutex;

int main(int argc, char **argv)

 HANDLE  hThread1, hThread2;
 DWORD dwThreadID1, dwThreadID2;

 hMutex = CreateMutex(NULL, FALSE, NULL);

 if(hMutex==NULL){
  puts("뮤텍스 오브젝트 생성 실패");
  exit(1);
 }
 
 hThread1 = (HANDLE)_beginthreadex(NULL, 0, ThreadIncrement,

(void*)thread1, 0, (unsigned *)&dwThreadID1);
 hThread2 = (HANDLE)_beginthreadex(NULL, 0, ThreadIncrement,

 (void*)thread2, 0, (unsigned *)&dwThreadID2);

 if(hThread1==0 || hThread2==0) {
  puts("쓰레드 생성 오류");
  exit(1);
 }

 if(WaitForSingleObject(hThread1, INFINITE)==WAIT_FAILED)
  ErrorHandling("쓰레드 wait 오류");
  
 if(WaitForSingleObject(hThread2, INFINITE)==WAIT_FAILED)
  ErrorHandling("쓰레드 wait 오류");
 
 printf("최종 number : %d \n", number);
 
 CloseHandle(hMutex); //Mutex 오브젝트 소멸
    return 0;
}

DWORD WINAPI ThreadIncrement(void *arg)
{
    int i;

    for(i=0; i<5; i++) {
       WaitForSingleObject(hMutex, INFINITE); //Mutex를 얻는다.
       Sleep(100);
       number++;
       printf("실행 : %s,  number : %d \n", (char*)arg, number);
       ReleaseMutex(hMutex);// Mutex를 반환한다.
    } 
    return 0;
}

void ErrorHandling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

 

(4)Semaphore에 의한 동기화기법

-특징:세마포어가 0이되어야 세마포어가 non_signaled상태가 된다

 

HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTES lpMutexAttributes,

                                       LONG lnitialCount,

                                       LONG  lMaxinumCount,

                                       LPCTSTR lpName);

 

BOOL      RelaseSemaphore(HANDLE hSemphore,

                                         LONG lReleaseCount,

                                         LPLONG lpPreviousCount); //시그널 상태로 바꾼다.(카운터 값을 하나 증가시켜주는 함수)

 

BOOL CloseHandle(HANDLE hObject);

 

여기서의 WaitForSingleObject호출의 의미:넌그시널드 상태로 바꾼다.(카운터값을 하나 감소 시켜준다)

 

(5)Semaphore에 의한 예제

/*
 * semaphore2_win.c
 * Written by SW. YOON
 */

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <process.h>    /* _beginthreadex, _endthreadex */

DWORD WINAPI ThreadSend(void *arg);
DWORD WINAPI ThreadRecv(void *arg);
void ErrorHandling(char *message);

HANDLE hSem, hSem2;
int number=0;

char thread1[]="A Thread";
char thread2[]="B Thread";
char thread3[]="C Thread";

int main(int argc, char **argv)
{
  HANDLE hThread1, hThread2, hThread3;
  DWORD dwThreadID1, dwThreadID2, dwThreadID3;

  hSem=CreateSemaphore(NULL, 0, 1, NULL); // 바이너리 세마포어 생성.
  hSem2=CreateSemaphore(NULL, 0, 1, NULL);
  if(hSem==NULL || hSem2==NULL) {
    puts("세마포어 오브젝트 생성 실패");
    exit(1);
  }

  hThread1 = (HANDLE)_beginthreadex(NULL, 0, ThreadSend, (void*)thread1, 0, (unsigned *)&dwThreadID1);
  hThread2 = (HANDLE)_beginthreadex(NULL, 0, ThreadRecv, (void*)thread2, 0, (unsigned *)&dwThreadID2);
  hThread3 = (HANDLE)_beginthreadex(NULL, 0, ThreadRecv, (void*)thread3, 0, (unsigned *)&dwThreadID3);
  if(hThread1==0 || hThread2==0 || hThread3==0) {
   puts("쓰레드 생성 오류");
   exit(1);
  }

  if(WaitForSingleObject(hThread1, INFINITE)==WAIT_FAILED)
   ErrorHandling("쓰레드 wait 오류");

  if(WaitForSingleObject(hThread2, INFINITE)==WAIT_FAILED)
   ErrorHandling("쓰레드 wait 오류");

  if(WaitForSingleObject(hThread3, INFINITE)==WAIT_FAILED)
   ErrorHandling("쓰레드 wait 오류");
 
  printf("최종 number : %d \n", number);
  CloseHandle(hSem); // hSem세마포어 오브젝트 소멸!
  CloseHandle(hSem2); // hSem2세마포어 오브젝트 소멸!

  return 0;
}

DWORD WINAPI ThreadSend(void * arg)
{
  int i;
  for(i=0; i<4; i++){
   number++;
   printf("실행 : %s,  number : %d \n", (char*)arg, number);
   ReleaseSemaphore(hSem, 1, NULL);  // hSem 세마포어 1 증가!
   WaitForSingleObject(hSem2, INFINITE); // hSem2 세마포어 1 감소!
  } 
  return 0;
}

DWORD WINAPI ThreadRecv(void * arg)
{
  int i;
  for(i=0; i<2; i++){
    WaitForSingleObject(hSem, INFINITE); // hSem 세마포어 1 감소!
    number--;
    printf("실행 : %s, number : %d \n", (char*)arg, number);
    ReleaseSemaphore(hSem2, 1, NULL); // hSem2 세마포어 1 증가!
  }
  return 0;
}

void ErrorHandling(char *message)
{
  fputs(message, stderr);
  fputc('\n', stderr);
  exit(1);
}

                             

(6)Event에 의한 동기화기법

-Event의 특징

:오토,매뉴얼리셋모드의 커널 오브젝트의 생성이 가능하다.다른 동기화기법은 오토만 가능하다.

매뉴얼리셋모드 동기화를 통해서 여러 개의 쓰레드를 동시에 깨워서 작업을 할 수 있다.

"여러 개의 쓰레드를 동시에 깨워야 한다면

Event 동기화기법 중 매뉴얼리셋모드를 사용하자"

 

HANDLE CreateEvent(LPSECURTIY_ATTRIBUTES lpEventAttributes,

                               BOOL bMannaulReset,

                               BOOL bInitialState,

                               LPCTSTR lpName);

 

BOOL ResetEvent(HANDLE hEvent);

 

(7)Event에 대한 예제

/*
 * event.c
 * Written by SW. YOON
 */

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <process.h>    /* _beginthreadex, _endthreadex */

DWORD WINAPI NumberOfA(void *arg);
DWORD WINAPI NumberOfOthers(void *arg);

void ErrorHandling(char *message);
char String[100];
HANDLE hEvent;

int main(int argc, char **argv)

 HANDLE  hThread1, hThread2;
 DWORD dwThreadID1, dwThreadID2;

 hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);//2번째인자:수동모드

//3번째 인자:넌그시널상태 
 if(hEvent==NULL){
  puts("Event 오브젝트 생성 실패");
  exit(1);
 }
 
 hThread1 = (HANDLE)_beginthreadex(NULL, 0, NumberOfA, NULL, 0, (unsigned *)&dwThreadID1);
 hThread2 = (HANDLE)_beginthreadex(NULL, 0, NumberOfOthers, NULL, 0, (unsigned *)&dwThreadID2);

 if(hThread1==0 || hThread2==0) {
  puts("쓰레드 생성 오류");
  exit(1);
 } 

 fputs("문자열을 입력 하세요 : ", stdout);
 fgets(String, 30, stdin);
 SetEvent(hEvent);//시그널 상태로 변경

 if(WaitForSingleObject(hThread1, INFINITE)==WAIT_FAILED) //
  ErrorHandling("쓰레드 wait 오류");
  
 if(WaitForSingleObject(hThread2, INFINITE)==WAIT_FAILED)
  ErrorHandling("쓰레드 wait 오류");

  CloseHandle(hEvent); //Event 오브젝트 소멸
    return 0;
}

DWORD WINAPI NumberOfA(void *arg)
{
  int i;
  int count=0;
 
  WaitForSingleObject(hEvent, INFINITE);

//Event를 얻는다. //시그널 상태가 되면 리턴
  for(i=0; String[i]!=0; i++) {
   if(String[i]=='A')
    count++;
  }

  printf("A 문자의 수 : %d\n", count);
  return 0;
}

DWORD WINAPI NumberOfOthers(void *arg)
{
  int i;
  int count=0;
 
  WaitForSingleObject(hEvent, INFINITE);

//Event를 얻는다. //시그널 상태가 되면 리턴 위아래쓰레드 모두 깨워진다.
  for(i=0; String[i]!=0; i++) {
   if(String[i]!='A')
    count++;
  }

  printf("A 이외의 문자 수 : %d\n", count-1);
  return 0;
}

void ErrorHandling(char *message)
{
  fputs(message, stderr);
  fputc('\n', stderr);
  exit(1);
}
 (8)윈도우즈 동기화를 이용한 채팅서버클라이언트예제

-서버

/*
 * chat_server_win.c
 * Written by SW. YOON
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#include <process.h>

#define BUFSIZE 100

DWORD WINAPI ClientConn(void *arg);
void SendMSG(char* message, int len);
void ErrorHandling(char *message);

int clntNumber=0;
SOCKET clntSocks[10];
HANDLE hMutex;

int main(int argc, char **argv)
{
  WSADATA wsaData;
  SOCKET servSock;
  SOCKET clntSock;

  SOCKADDR_IN servAddr;
  SOCKADDR_IN clntAddr;
  int clntAddrSize;

  HANDLE hThread;
  DWORD dwThreadID;

  if(argc!=2){
    printf("Usage : %s <port>\n", argv[0]);
    exit(1);
  }
  if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) /* Load Winsock 2.2 DLL */
   ErrorHandling("WSAStartup() error!");

  hMutex=CreateMutex(NULL, FALSE, NULL);
  if(hMutex==NULL){
   ErrorHandling("CreateMutex() error");
  }

  servSock=socket(PF_INET, SOCK_STREAM, 0);  
  if(servSock == INVALID_SOCKET)
    ErrorHandling("socket() error");
 
  memset(&servAddr, 0, sizeof(servAddr));
  servAddr.sin_family=AF_INET;
  servAddr.sin_addr.s_addr=htonl(INADDR_ANY);
  servAddr.sin_port=htons(atoi(argv[1]));

  if(bind(servSock, (SOCKADDR*) &servAddr, sizeof(servAddr))==SOCKET_ERROR)
    ErrorHandling("bind() error");

  if(listen(servSock, 5)==SOCKET_ERROR)
    ErrorHandling("listen() error");

  while(1){
   clntAddrSize=sizeof(clntAddr);
   clntSock=accept(servSock, (SOCKADDR*)&clntAddr, &clntAddrSize);
   if(clntSock==INVALID_SOCKET)
    ErrorHandling("accept() error");

   WaitForSingleObject(hMutex, INFINITE);
   clntSocks[clntNumber++]=clntSock;
   ReleaseMutex(hMutex);
   printf("새로운 연결, 클라이언트 IP : %s \n", inet_ntoa(clntAddr.sin_addr));

   hThread = (HANDLE)_beginthreadex(NULL, 0, ClientConn, (void*)clntSock, 0, (unsigned *)&dwThreadID);
   if(hThread == 0) {
    ErrorHandling("쓰레드 생성 오류");
   }
  }

  WSACleanup();
  return 0;
}

DWORD WINAPI ClientConn(void *arg)
{
  SOCKET clntSock=(SOCKET)arg;
  int strLen=0;
  char message[BUFSIZE];
  int i;

  while( (strLen=recv(clntSock, message, BUFSIZE, 0)) != 0)
    SendMSG(message, strLen);

  WaitForSingleObject(hMutex, INFINITE);
  for(i=0; i<clntNumber; i++){   // 클라이언트 연결 종료시
    if(clntSock == clntSocks[i]){
      for( ; i<clntNumber-1; i++)
    clntSocks[i]=clntSocks[i+1];
      break;
    }
  }
  clntNumber--;
  ReleaseMutex(hMutex);

  closesocket(clntSock);
  return 0;
}

void SendMSG(char* message, int len)
{
 int i;
 WaitForSingleObject(hMutex, INFINITE);
 for(i=0; i<clntNumber; i++)
  send(clntSocks[i], message, len, 0); //보내는 도중에 다른 쓰레드에서 클라이언트 소켓이 닫힐 수 있기 때문에
 ReleaseMutex(hMutex); 
}

void ErrorHandling(char *message)
{
  fputs(message, stderr);
  fputc('\n', stderr);
  exit(1);
}

-클라이언트

/*
 * chat_client_win.c
 * Written by SW. YOON
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#include <process.h>

#define BUFSIZE 100
#define NAMESIZE 20

DWORD WINAPI SendMSG(void *arg);
DWORD WINAPI RecvMSG(void *arg);
void ErrorHandling(char *message);

char name[NAMESIZE]="[Default]";
char message[BUFSIZE];

int main(int argc, char **argv)
{
  WSADATA wsaData;
  SOCKET sock;
  SOCKADDR_IN servAddr;
 
  HANDLE hThread1, hThread2;
  DWORD dwThreadID1, dwThreadID2;

  if(argc!=4){
    printf("Usage : %s <IP> <port> <name>\n", argv[0]);
    exit(1);
  }
  if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) /* Load Winsock 2.2 DLL */
   ErrorHandling("WSAStartup() error!");
  sprintf(name, "[%s]", argv[3]);

  sock=socket(PF_INET, SOCK_STREAM, 0);
  if(sock == INVALID_SOCKET)
    ErrorHandling("socket() error");

  memset(&servAddr, 0, sizeof(servAddr));
  servAddr.sin_family=AF_INET;
  servAddr.sin_addr.s_addr=inet_addr(argv[1]);
  servAddr.sin_port=htons(atoi(argv[2]));
 
  if(connect(sock, (SOCKADDR*)&servAddr, sizeof(servAddr))==SOCKET_ERROR)
   ErrorHandling("connect() error");
 
  hThread1 = (HANDLE)_beginthreadex(NULL, 0, SendMSG, (void*)sock, 0, (unsigned *)&dwThreadID1);
  hThread2 = (HANDLE)_beginthreadex(NULL, 0, RecvMSG, (void*)sock, 0, (unsigned *)&dwThreadID2);
  if(hThread1==0 || hThread2==0) {
   ErrorHandling("쓰레드 생성 오류");
  }
 
  WaitForSingleObject(hThread1, INFINITE);
  WaitForSingleObject(hThread2, INFINITE);
 
  closesocket(sock);
  return 0;
}

DWORD WINAPI SendMSG(void *arg) // 메시지 전송 쓰레드 실행 함수
{
   SOCKET sock = (SOCKET)arg;
   char nameMessage[NAMESIZE+BUFSIZE];
   while(1) {
      fgets(message, BUFSIZE, stdin);
      sprintf(nameMessage,"%s %s", name, message);
      if(!strcmp(message,"q\n")) {  // 'q' 입력시 종료
         closesocket(sock);
         exit(0);  
      }
      send(sock, nameMessage, strlen(nameMessage), 0);
   }
}

DWORD WINAPI RecvMSG(void *arg) /* 메시지 수신 쓰레드 실행 함수 */
{
  SOCKET sock = (SOCKET)arg;
  char nameMessage[NAMESIZE+BUFSIZE];
  int strLen;
  while(1){
    strLen = recv(sock, nameMessage, NAMESIZE+BUFSIZE-1, 0);
 if(strLen==-1) return 1;
 
    nameMessage[strLen]=0;
    fputs(nameMessage, stdout);
  }
}

void ErrorHandling(char *message)
{
  fputs(message, stderr);
  fputc('\n', stderr);
  exit(1);
}

 

반응형

'TCP/IP > 윈도우기반' 카테고리의 다른 글

비동기 Notification입출력모델  (0) 2009.10.08
Completion Port 입출력 모델  (0) 2009.10.08
IOCP  (0) 2009.10.08
TCP 파일 전송 클라이언트 <윈도우 기반>  (0) 2009.07.01
TCP 파일 수신 서버<윈도우 기반>  (0) 2009.07.01

댓글