운영체제와 프로세스, 쓰레드의 관계
- 하나의 운영체제 내에서는 둘 이상의 프로세스가 생성되고, 하나의 프로세스 내에서는 둘 이상의 쓰레드가 생성된다.
- 운영체제가 만드는 리소스의 유형
- 프로그램의 실행과 관련된 프로세스와 쓰레드
- 입출력의 도구가 되는 소켓과 파일
- 쓰레드간 동기화의 도구로 사용되는 세마포어 뮤텍스 - 커널 오브젝트의 소유자
- 커널 오브젝트의 생성, 관리 및 소멸은 운영체제가 담당한다.
- 즉, 커널 오브젝트의 소유자는 운영체제이다.
윈도우 기반의 쓰레드 생상
- 리눅스와 달리 윈도우의 쓰레드는 쓰레드 함수를 반환하면 자동으로 소멸
#include <process.h>
unsigned WINAPI ThreradFunc(void *arg);
int main(int argc, char *argv[])
{
HANDLE hThread;
unsigned threadID;
int param = 5;
hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, (void*)¶m, 0, &threadID);
// ThreadFunc -> WINAPI 쓰레드 함수, ㅔparam 매개변수, threadID 리턴값
if(hThread == 0)
{
puts("_beginthreadex() error");
return -1;
}
Sleep(3000);
puts("end of main");
rerturn 0;
}
unsigned WINAPI ThreadFunc(void *arg) // 쓰레드 함수
{
//쓰레드가 실행할 프로세스
int i;
int cnt =*((int*)arg);
for (i = 0 ; i<cnt; i++)
{
Sleep(1000); puts("running thread");
}
return 0;
}
Critical Section 과 동기화 기법
- CRITICAL SECTION (임계영역)
- 베타적 접근(한 순간에 하나의 쓰레드만 접근)이 요구되는 모든 쓰레드가 공유해야하는 리소스에 접근하는 코드 블록
- 임계영역에 접근하기 위해서는 하나의 쓰레드가 모든 작업을 완전하게 마친 후에 접근이 가능해야함 -> 쓰레드 동기화 - 유저모드 동기화
- 운영체제에 의해서 이뤄지는 동기화가 아닌, 순수 라이브러리에 의존해서 완성되는 동기화 기법
- 운영체제에 의해서 제공되지 않으므로 커널모드로의 전환이 불필요 -> 상대적으로 가볍고 속도가 빠르다.
- 커널모드의 기능을 사용 불가능하기 때문에 기능상 제한되는 부분이 있음 - 커널모드 동기화
- 커널모드 동기화는 커널에 의해서 제공이 되는 동기화 기법이다.
- 커널에 의해 제공되는 동기화이다 보니, 다양한 기능이 제공
- Dead-lock에 걸리지 않도록 타임아웃을 지정 가능
Mutex 를 이용한 동기화
- 커널 오브젝트의 생성
- 커널 오브젝트의 소멸
- Mutex 오브젝트의 획득과 반납
- Mutex 오브젝트는 커널 오브젝트이기 때문에 획득을 위해서 별도로 정의된 함수가 없다.
- 단, WaitForSingleObject 함수를 이용한다. --> Mutex 오브젝트는 획득 가능한 상태가 되면, signaled 상태가 된다.
WaitForSingleObject(hMutex, INFINITE);
// 임계영역의 시작
// 독점할 프로세스 내용
// 임계영역의 끝
ReleaseMutex(hMutex);
- Mutex 기반의 동기화 예
- SyncMutex_win.c
long long num = 0;
HANDLE hMutex;
int main(int argc, char *argv[])
{
HANDLE tHandles[NUM_THREAD];
int i;
hMutex = CreateMutex(NULL, FALSE, NULL); //signaled 상태, 이름없는 Mutext오브젝트 생성
for (i = 0; i<NUM_THREAD; i++)
{
{// 쓰레드 실행
if(i%2)
tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadInc, NULL, 0, NULL);
else
tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadDes, NULL, 0, NULL);
}
}
WaitForrMultipleObjects(NUM)THREAD, tHandles, TRUE, INFINITE); //임계영역 시작
CloseHandle(hMutex); //임계영역 종료
printf("result: %lld \n", num)
return 0
}
unsigend WINAPI threadInc(void * arg)
{
int i;
//signaled 상태 ->wait함수로 non-signaled 변환후 임계 접근
WaitForSingleObject(hMutex, INFINITE);
for(i = 0; i<5000000; i++)
num+=1;
ReleaseMutex(hMutex); //반납 (signaled)
return 0;
}
unsigned WINAPI threradDes(Void * arg)
{
int i;
// theadInc에서signaled 상태로 변환시키면 wait 함수가 반환 임계영역 접근 가능
WaitForSingleObject(hMutex, INFINITE);
for(i=0; i<5000000; i++)
num-=1;
ReleaseMutes(hMutex);
return 0;
멀티쓰레드 기반의 다중접속 서버
//chat_serv.c
int clntCnt = 0;
SOCKET clntSocks[MAX_CLNT];
HANDLE hMutex;
while(1)
{ // 하나의 쓰레드 생성
clntAdrSz=sizeof(clntAdr);
hClntSock=accpet(hServSock, (SOCKADDRR*)&clntAdr, &clntAdrSz);
//클라이언트의 정보가 저장된 공간에 접근하는 것을 동기화
WaitForSingleObject(hMutex, INFINITE);
clntSocks[clntCnt++]=hClntSock;
ReleaseMutex(hMutex);
hThread = (HANDLE)_beginthreadex(NULL, 0, HandleClnt, (void*)&hClntSock, 0, NULL);
printf("Connected client IP: %s \n", inet_ntoa(clntAd.sin_addr);
}
unsigned WINAPI HandleClnt(void *arg)
{
SOCKET hClntSock =*((SOCKET*)arg);
int strLen = 0, i;
char msg[BUF_SIZE];
while((strLen=recv(hClntSock, msg, sizeof(msg), 0))!=0)
SendMsg(msg, strLen);
WaitForSingleObject(hMutex, INFINITE);
for(i=0; i<clntCnt; i++) //remove disconnected client
{
if(hClntSock == clntSocks[i])
{
while(i++<clntCnt-1)
clntSocks[i] = clntSocks[i+1];
break;
}
}
clntCnt--;
ReleasMutex(hMutex);
closesocket(hClntSock);
return 0;
}
//모두에게 메세지를 보내는 함수
void SendMsg(char *msg, int len) //echo to all
{
int i;
// signaled -> non-signaled
WaitForSingleObject(hMutex, INFINITE);
for(i=0; i<clntCnt; i++)
send(clntSocks[i], msg, len, 0);
ReleaseMutex(hMutex); non-signaled -> signaled
}
//하나의 뮤텍스를 대상으로 두 영역에서 동기화를 진행
멀티쓰레드 기반의 다중접속 클라이언트
//chat_clnt_win.c
if(connect(hSock, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR)
ErrorHandling("connect() error");
//데이터 송신과 수신 각각 쓰레드를 할당
hSndThread = (HANDLE)_beginthreadex(NULL, 0, SendMsg, (void*)&hSock, 0, NULL); //송신 쓰레드
hRcvThread = (HANDLE)_beginthreadex(NULL, 0, RecvMsg, (void*)&hSock, 0, NULL); //수신 쓰레드
WaitForSingleObject(hSndThread, INFINITE); //non-signaled 준비상태
WaitForSingleObject(hRcvTHread, INFINITE); //non-signaled 준비상태
closesocket(hSock);
WSACleanup();
return 0;
unsigned WINAPI RecvMsg(void *arg) // 수신 쓰레드의 수행 함수
{
int hSock =*((SOCKET*)arg); // arg : 소켓정보
char nameMsg[NAME_SIZE+BUF_SIZE];
int strLen;
while(1)
{
strLen = recv(hSock, nameMsg, NAME_SIZE+BUF_SIZE-1, 0); //서버로부터 문자열 받아옴
if(strLen == -1)
ruturn -1;
nameMsg[strLen]=0;
fputs(nameMsg, stdout); //메세지 출력
}
return 0;
}
unsinged WINAPI SendMsg(void *arg) // 송신 쓰레드의 수행 함수
{
SOCKET hSock =*((SOCKET*)arg);
char namesg[NAME_SIZE+BUF_SIZE];
while(1)
{
fgets(msg, BUF_SIZE, stdin);
if(!strcmp(msg, "q\n")||!strcmp(msg,"Q\n"))
{
closesocket(hSock);
exit(0);
}
sprintf(nameMsg, "%s %s", name, msg); //이름과 메세지 입력
sned(hSock, nameMsg, strlen(nameMsg), 0); //서버로 메세지 송신
}
return 0;
}