[Network] 18. 멀티쓰레드 기반의 서버구현

2023. 5. 29. 00:45

쓰레드의 이론적 이해

쓰레드의 등장배경

프로세스의 생성에는 많은 리소스가 소모되고, context switching으로 성능이 저하된다. 그리고 프로세스간 메모리가 독립적으로 운영되어 데이터 공유가 어렵다. 

그래서 프로세스보다 가볍고 경량화된 프로세스인 쓰레드가 탄생했다. 

위의 사진처럼 쓰레드는 완전히 독립적인 실행 흐름을 가지고 있다. 하지만 쓰레드는 아래 사진처럼 프로세스 내에 형성된다. 그래서 context switching의 부담이 덜하며, 데이터 교환이 쉽다.

 

쓰레드의 생성 및 실행

쓰레드의 생성

#include <pthread.h>

int pthread_create(
	pthread_t *restrict thread, const pthread_attr_t *restrict attr, 
	void*(*start_routine)(void*), void*restrict arg
);

쓰레드 종료 대기

#include <pthread.h>

int pthread_join(pthread_t thread, void **status);

 

임계영역 내에서 호출이 가능한 함수

쓰레드에 안전한 함수가 있고 불안전한 함수가 있다. 
쓰레드에 안전한 함수를 Thread-safe function , 불안전한 함수를 Thread-unsafe function이라고 한다.

gcc compile option에 -D_REENTRANT 을 추가하면 자동으로 thread-unsafe를 thread-safe function으로 변경된다.

 

워커(Worker) 쓰레드 모델

할 일이 생기면 thread를 생성시키고 그 결과를 취합하는 형태를 worker thread model이라고 한다.

 

쓰레드의 문제점과 임계영역

둘 이상의 thread가 한 변수에 접근할 때, 한 상태가 적용되지 않은 상태로 접근을 하면 문제가 일어난다(동시성 문제)

그래서 동일한 메모리 영역을 접근해야할 때에는 동시 접근을 막아야한다. 

 

쓰레드의 동기화

쓰레드 동기화는 두 가지 상황에서 이루어져야한다.

  • 동일한 메모리 영역으로의 동시 접근이 발생하는 상황
  • 동일한 메모리 영역에 접근하는 쓰레드의 실행순서를 지정해야하는 상황

두 가지 동기화 기법이 있다. 

1. Mutex(Mutex Exclusive) 기반 동시화
2. Semaphore 기반 동기화

 

뮤텍스 기반 동기화

- 뮤텍스의 생성과 소멸

#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t * attr);
int phread_mutex_destroy(pthread_mutex_t *mutex);

 

- 뮤텍스의 획득과 반환

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

 

세마포어 기반 동기화

세마포어는 세마포어 카운트 값을 통해서 임계 영역에 동시접근 가능한 쓰레드의 수를 제한하여 동기화를 시켜준다.

- 세마포어 생성과 소멸

#include <semaphore.h>

int sem_init(sem_t * sem, int pshared, unsigned int value);
int sem_destroy(sem_t *sem);

sem_init의 value는 semaphore의 초기 값을 설정한다.

- 세마포어 획득과 반환

#include <semaphore.h>

int sem_post(sem_t *sem);
int sem_wait(sem_t *sem);

 

세마포어 카운트가 0이며 진입불가, 0보다 크면 진입가능이다. 그래서 post하면 세마포어 값이 1로 변하고 wait하면 0으로 바꾼다. 

 

쓰레드의 소멸

쓰레드 함수가 반환을 해도 자동으로 소멸되지 않기 때문에 pthread_join이나 pthread_detach 중 하나를 호출 해서 쓰레드의 소멸을 도와야한다.

#include <pthread.h>

int pthread_detach(pthread_t thread);

 

 

쓰레드 기반 서버의 구현

멀티 쓰레드 기반의 다중접속 서버의 구현

//main
while(1)
{
	clnt_adr_sz = sizeof(clnt_adr);
    clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
    
    pthread_mutex_lock(&mutx);
    clnt_socks[clnt_cnt++]=clnt_sock;
    pthread_mutex_unlock(&mutx);
    
    pthread_create(&t_id, NULL, handle_clnt, (void*)&clnt_sock);
    pthread_detack(t_id);
    printf("Connected client IP : %s \n", inet_ntoa(clnt_adr.sin_addr));
}

void * handle_clnt(void *arg)
{
	int clnt_sock=*((int*)arg);
    int str_len=0, i;
    char msg[BUFSIZE];
    
    while((str_len = read(clnt_sock, msg, sizeof(msg)))!=0)
    	send_msg(msg, str_len);
    pthread_mutex_lock(&mutx);
    for(i = 0; i< clnt_cnt; i++)
    {
    	if(clnt_sock==clnt_socks[i])
        {
        	while(i++ <clnt_cnt-1)
            	clnt_socks[i] = clnt_socks[i+1];
        	break;
        }
    }
    clnt_cnt--;
    pthread_mutex_unlock(&mutx);
    close(clnt_sock);
    return NULL;
}

void send_msg(char * msg, int len)
{
	int i;
    pthread_mutex_lock(&mutx);
    for(i=0;i< clnt_cnt; i++)
    	write(clnt_socks[i], msg, len);
    pthread_mutx_unlock(&mutx);
}

 

멀티 쓰레드 기반의 채팅 클라이언트의 구현

//main
if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1)
	error_handling("connect() error");
pthread_create(&snd_thread, NULL, send_msg, (void*)&sock);
pthread_create(&rcv_thread, NULL, recv_msg, (void*)&sock);
pthread_join(snd_thread, &thread_return);
pthread_join(rcv_thread, &thread_return);
close(sock);




//recv_msg
void * recv_msg(void * arg)
{
	int sock=*((int*)arg);
    char name_msg[NAMESIZE + BUFSIZE];
    int str_len;
    while(1)
    {
    	str_len = read(sock, name_msg, NAMESIZE + BUFSIZE -1);
        if(strlen==-1)
        	return (void*)-1;
        name_msg[str_len] =0;
        fputs(name_msg, stdout);
    }
    return NULL;
}

//send_msg
void * send_msg(void * arg)
{
	int sock=*((int*)arg);
    char name_msg[NAMESIZE + BUFSIZE];
    while(1)
    {
    	fgets(msg, BUFSIZE, stdin);
        if(!strcmp(msg, "q\n") || !strcmp(msg, "Q\n"))
        {
        	close(sock);
            exit(0);
        }
        sprintf(name_msg, "%s %s", name, msg);
        write(sock, name_msg, strlen(name_msg));
    }
    return NULL;
}

BELATED ARTICLES

more