260 likes | 799 Views
TCP/IP Socket Programming…. 제 17 장 멀티쓰레드 기반의 서버구현. 데이터베이스 실험실 석사 2 학기 김기훈 Khkim@dblab.hannam.ac.kr. 목차. 쓰레드란 무엇인가 쓰레드 생성하기 다중 쓰레드 생성하기 임계영역 & 쓰레드의 문제점 뮤텍스 (mutex) 세마포어 (Semaphore) 쓰레드 기반 서버 구현. Thread Stack. Thread Stack. Thread Stack. Thread Stack. Global Variables. Heap.
E N D
TCP/IP Socket Programming… 제 17장 멀티쓰레드 기반의 서버구현 데이터베이스 실험실 석사 2학기 김기훈 Khkim@dblab.hannam.ac.kr
목차 • 쓰레드란 무엇인가 • 쓰레드 생성하기 • 다중 쓰레드 생성하기 • 임계영역 & 쓰레드의 문제점 • 뮤텍스(mutex) • 세마포어(Semaphore) • 쓰레드 기반 서버 구현
Thread Stack Thread Stack Thread Stack Thread Stack Global Variables Heap Global Variables Heap 쓰레드란 무엇인가[1] • 경량화 된 프로세스 • 프로세스와 마찬가지로 동시실행이 가능함 • 프로세스의 단점을 극복하기 위해 등장 • 프로세스와의 차이점 • 스텍을 제외한 나머지 메모리 공간을 공유 • 보다 간단한 컨텍스트 스위칭(context switching) • 일부메모리를 공유하므로 스레드간 통신이 편리 Process Process Thread 1 Thread Thread 1 Thread 1
쓰레드란 무엇인가[2] • 프로세스와 쓰레드
#include <pthread.h> int pthread_create (pthead_t * thread, pthread_attr-t * attr, void * (* start_routine) (void *), void * arg ); 성공 시 0, 실패 시 이외의 값 리턴 쓰레드 생성하기[1] • 쓰레드를 생성하는 함수 • Pthread_create 함수 • thread : 생성된 쓰레드의 ID를 저장할 변수의 포인터를 인자로 전달 • Attr : 생성하고자 하는 쓰레드의 특성(attribute)을 설정할 때 사용, 일반적으로 Null을 전달 • Start_routine : 리턴타입과 인자가 void*인 함수를 가르키는 포인터 • Arg : 쓰레드에 의해 호출되는 함수에 전달하고자 하는 인자값을 넘겨줌
실행결과 Process 쓰레드 생성 Thread 종료 종료 쓰레드 생성하기[2] • thread1.c void *thread_function(void *arg); int main(int argc, char **argv){ int state; pthread_t t_id; void *t_return; state = pthread_create(&t_id, NULL, thread_function, NULL); if(state != 0){ puts("쓰레드 생성 오류"); exit(1); } printf("생성된 쓰레드 ID : %d \n", t_id); sleep(3); puts("main함수 종료"); return 0; } void * thread_function(void *arg) { int i; for(i=0; i<3; i++){ sleep(2); puts("쓰레드 실행 중"); } }
#include <pthread.h> int pthread_join(pthead_t * th, void **thread_return); ); 성공 시 0, 실패 시 이외의 값 리턴 쓰레드 생성하기[3] • pthread_join 함수 • th : th에 인자로 들어오는 ID의 쓰레드가 종료할 때까지 실행 지연 • thread_return : 쓰레드가 종료 시 반환하는 값에 접근할 수 있는 2차원포인터
Process 실행결과 쓰레드 생성 Thread JOIN 대기상태 RETURN 종료 종료 쓰레드 생성하기[4] • thread2.c int main(int argc, char **argv){ . . . void *t_return; state = pthread_create(&t_id, NULL, thread_function, NULL); . . . printf("생성된 쓰레드 ID : %d \n", t_id); /* 쓰레드 종료 시까지 main함수의 실행을 지연 */ state = pthread_join(t_id, &t_return); /* 리턴 값 저장 */ if(state !=0 ){ puts("쓰레드 Join 오류"); exit(1); } printf("main함수 종료, 쓰레드 리턴 %s", (char*)t_return); free(t_return); return 0; } void * thread_function(void *arg) { int i; char *p = (char*)malloc(20*sizeof(char)); strcpy(p, "쓰레드 종료됨 !\n"); for(i=0; i<3; i++){ sleep(2); puts("쓰레드 실행 중"); } return p; }
다중 쓰레드 생성하기[1] • 임계영역(Critical Section)과 쓰레드에 안전한 함수의 호출 • 임계영역 • 두개 이상의 쓰레드에 의해서 동시에 실행되면 안 되는 영역 • 쓰레드 관점에서 볼 때 함수의 종류 • 쓰레드 불안전한 함수(Thread-unsafe Function) • 단일 쓰레드 모델에서는 사용 가능함 함수이지만 다중 쓰레드 모델에서는 사용할 수 없는 함수(gethostbyname) • 쓰레드 안전한 함수(Thread-safe Function) • 다중 쓰레드 모델에서 사용 가능한 함수(gethostbyname_r) • 불안전한 함수를 안전한 함수로 변경 • 컴파일시 –D_REENTRANT를 옵션으로 넣어 주는 방식으로 매크로를 선언
Process 쓰레드 생성 Thread 쓰레드 생성 Thread JOIN RETURN 종료 JOIN 실행결과 종료 종료 다중 쓰레드 생성하기[2] • Thread3.c void *thread_summation(void *arg); int sum=0; int sum1[]={1, 5}; int sum2[]={6, 10}; int main(int argc, char **argv){ pthread_t id_t1, id_t2; void *t_return; pthread_create(&id_t1, NULL, thread_summation, (void *)sum1); pthread_create(&id_t2, NULL, thread_summation, (void *)sum2); /* 쓰레드 종료 시까지 main함수의 실행을 지연 */ pthread_join(id_t1, &t_return); pthread_join(id_t2, &t_return); printf("main함수 종료, sum = %d \n", sum); return 0; } void * thread_summation(void *arg){ int start = ((int*)arg)[0]; int end = ((int*)arg)[1]; for(; start<=end; start++){ sum+=start; } }
실행결과 다중 쓰레드 생성하기[3] • Thread4.c #define NUMBER 10000 void *thread_increment(void *arg); int num=0; int main(int argc, char **argv){ int i; pthread_t thread_id[10]; void *t_return; for(i=0; i<10; i++) pthread_create(&thread_id[i], NULL, thread_increment, NULL); /* 생성한 모든 쓰레드 종료 시까지 main 함수의 실행을 지연 */ for(i=0; i<10; i++) pthread_join(thread_id[i], &t_return); printf("main함수 종료, num=%d \n", num); return 0; } void *thread_increment(void *arg){ int i; for(i=0; i<NUMBER; i++) num++; }
임계영역 & 쓰레드의 문제점[1] • 컴퓨터가 덧셈하는 원리 int i = 10 int j = 20 j+=i
임계영역 & 쓰레드의 문제점[2] • 두 개의 쓰레드에 의한 덧셈 원리 int i = 10 . . . . . i+=10
임계영역 & 쓰레드의 문제점[3] • 임계영역 • 두개 이상의 쓰레드에 의해서 공유되는 메모리 공간에 접근하는 코드영역 • 쓰레드의 동기화 • 공유된 메모리에 둘 이상의 쓰레드가 동시 접근하는 것을 막는 방법 • 둘 이상의 쓰레드 실행 순서를 컨트롤하는 방법 • 대표적인 동기화 기법 • 뮤텍스 • 세마포어
뮤텍스(Mutex)[1] • 뮤텍스 • Mutual Exclusion의 줄임말로 쓰레드들의 동시접근을 허용하지 않겠다는 의미 • Pthread_mutex_t 타입변수를 가르켜 흔히 뮤텍스라고 함 • 뮤텍스의 기본원리 • 임계영역에 들어갈 때 뮤텍스를 잠그고 들어감 • 임계영역을 빠져 나올 때 뮤텍스를 풀고 나옴 • 뮤텍스 조작함수 • 초기화 : pthread_mutex_init (pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr) • 잠금 : pthread_mutex_lock (pthread_mutex_t *mutex) • 잠금 해제 : pthread_mutex_unlock (pthread_mutex_t *mutex) • 소멸 : pthread_mutex_destroy (pthread_mutex_t *mutex)
Thread B Thread A 임계영역 임계영역 Thread B 진입 Thread B Thread A 임계영역 Thread A 진입상태 뮤텍스(Mutex)[2] • 뮤텍스의 동기화 원리 2.Thread B 진입 pthread_mutex_lock 함수 호출 후 임계영역에 진입 [3] [1] Pthread_mutux_lock 함수 호출 후 대기상태 1. Pthread_mutex_unlock 함수 호출 후 임계 영역 탈출 [2]
뮤텍스(Mutex)[3] • mutex.c void *thread_increment(void *arg); char thread1[] = "A Thread"; char thread2[] = "B Thread"; pthread_mutex_t mutx; int number = 0; int main(int argc, char **argv){ pthread_t t1, t2; void *thread_result; int state; state = pthread_mutex_init(&mutx, NULL); if(state){ puts("뮤텍스 초기화 실패"); exit(1); } pthread_create(&t1, NULL, thread_increment, &thread1); pthread_create(&t2, NULL, thread_increment, &thread2); pthread_join(t1, &thread_result); pthread_join(t2, &thread_result); printf("최종 number : %d \n", number); pthread_mutex_destroy(&mutx); return 0; } void *thread_increment(void * arg) { int i; for(i=0; i<5; i++){ pthread_mutex_lock (&mutx); sleep(1); number++; printf ("실행 : %s, number : %d \n", (char*)arg, number); pthread_mutex_unlock (&mutx); } } 실행결과
세마포어(Semaphore)[1] • 세마포어 • sem_t 타입의 변수를 가르켜 흔히 세마포어라고 함 • 세마포어의 기본원리 • 정수를 가짐 • 정수 값이 0 이면 실행 불가능 • 세마포어가 1 이상이면 실행 가능 • 세마포어는 0 미만은 될수 없고 1 이상은 가능 • 세마포어 조작 함수 • 초기화 : sem_init (sem_t *sem, int pshared, unsigned int value) • 소멸 : sem_destory (sem_t *sem) • 증가 : sem_wait (sem_t *sem) • 감소 : sem_post (sem_t *sem)
Thread A Thread A Thread B Data Data Thread B 세마포어(Semaphore)[2] • 세마포어의 동기화 원리 1.새로운 데이터를 저장 후 세마포어 하나 증가 2.새로운 데이터를 저장 후 세마포어 하나 증가 1. 세마포어가 현재 0이므로 sem_wait 함수 호출 시 대기 상태로 진입 3. 실행 상태로 돌아와 세마포어를 하나 감소 후 데이터 얻음. 2. 세마포어 값을 하나 감소 시킨 후, 데이터를 가져간다. [1] [2]
세마포어(Semaphore)[3] • semaphore.c void *thread_snd(void * arg){ int i; for(i=0; i<4; i++){ while(number != 0); sleep(1); number++; printf("실행 : %s, number : %d \n", (char*)arg, number); sem_post(&bin_sem); } } void *thread_rcv(void * arg){ int i; for(i=0; i<2; i++){ sem_wait(&bin_sem); number--; printf("실행 : %s, number : %d \n", (char*)arg, number); } } void *thread_snd(void *arg); void *thread_rcv(void *arg); sem_t bin_sem; int number = 0; char thread1[] = "A Thread"; char thread2[] = "B Thread"; char thread3[] = "C Thread"; int main(int argc, char **argv){ pthread_t t1, t2, t3; void *thread_result; int state; state = sem_init(&bin_sem, 0, 0); //bin_sem은 0으로 설정 if(state != 0){ puts("세마포어 초기화 실패"); exit(1); } pthread_create(&t1, NULL, thread_snd, &thread1); pthread_create(&t2, NULL, thread_rcv, &thread2); pthread_create(&t3, NULL, thread_rcv, &thread3); pthread_join(t1, &thread_result); pthread_join(t2, &thread_result); pthread_join(t3, &thread_result); printf("최종 number : %d \n", number); sem_destroy(&bin_sem); return 0; } 실행결과
세마포어(Semaphore)[4] • semaphore2.c void *thread_snd(void * arg){ int i; for(i=0; i<4; i++){ number++; printf("실행 : %s, number : %d \n", (char*)arg, number); sem_post(&bin_sem); sem_wait(&bin_sem2); } } void *thread_rcv(void * arg){ int i; for(i=0; i<2; i++){ sem_wait(&bin_sem); number--; printf("실행 : %s, number : %d \n", (char*)arg, number); sem_post(&bin_sem2); } } void *thread_snd(void *arg); void *thread_rcv(void *arg); sem_t bin_sem bin_sem2; int number = 0; char thread1[] = "A Thread"; char thread2[] = "B Thread"; char thread3[] = "C Thread"; int main(int argc, char **argv){ pthread_t t1, t2, t3; void *thread_result; int state; state = sem_init(&bin_sem, 0, 0); //bin_sem은 0으로 설정 if(state != 0){ puts("세마포어 초기화 실패"); exit(1); } pthread_create(&t1, NULL, thread_snd, &thread1); pthread_create(&t2, NULL, thread_rcv, &thread2); pthread_create(&t3, NULL, thread_rcv, &thread3); pthread_join(t1, &thread_result); pthread_join(t2, &thread_result); pthread_join(t3, &thread_result); printf("최종 number : %d \n", number); sem_destroy(&bin_sem); return 0; } 실행결과
쓰레드 기반 서버 구현[1] • chat_server.c #define BUFSIZE 100 void *clnt_connection(void *arg); void send_message(char *message, int len); void error_handling(char * message); int clnt_number = 0; int clnt_socks[10]; pthread_mutex_t mutx; int main(int argc, char **argv){ int serv_sock; int clnt_sock; struct sockaddr_in serv_addr; struct sockaddr_in clnt_addr; int clnt_addr_size; pthread_t thread; . . . . while(1){ clnt_addr_size = sizeof(clnt_addr); clnt_sock = accept(serv_sock, (struct sockaddr*) &clnt_addr, &clnt_addr_size); pthread_mutex_lock(&mutx); clnt_socks[clnt_number++] = clnt_sock; pthread_mutex_unlock(&mutx); pthread_create(&thread, NULL, clnt_connection, (void*)clnt_sock); printf("새로운 연결, 클라이언트 ip : %s \n", inet_ntoa(clnt_addr.sin_addr)); } return 0; } void *clnt_connection(void *arg){ int clnt_sock = (int)arg; int str_len = 0; char message[BUFSIZE]; int i; while((str_len = read(clnt_sock, message, sizeof(message))) != 0) send_message(message, str_len); pthread_mutex_lock(&mutx); for(i=0; i<clnt_number; i++){ /*클라이언트 연결 종료 시 */ if(clnt_sock == clnt_socks[i]){ for(; i<clnt_number-1; i++) clnt_socks[i] = clnt_socks[i+1]; break; } } clnt_number--; pthread_mutex_unlock(&mutx); close(clnt_sock); return 0; } void send_message(char * message, int len){ int i; pthread_mutex_lock(&mutx); for(i=0; i<clnt_number; i++) write(clnt_socks[i], message, len); pthread_mutex_unlock(&mutx); }
쓰레드 기반 서버 구현[2] • chat_client.c void *send_message(void *arg){ /*메시지 전송 쓰레드 실행 함수*/ int sock = (int)arg; char name_message[NAMESIZE+BUFSIZE]; while(1){ fgets(message, BUFSIZE, stdin); if(!strcmp(message, "q\n")){ /* 'q' 입력 시 종료 */ close(sock); exit(0); } sprintf(name_message, "%s %s", name, message); write(sock, name_message, strlen(name_message)); } } void *recv_message(void *arg){ int sock = (int)arg; char name_message[NAMESIZE+BUFSIZE]; int str_len; while(1){ str_len = read(sock, name_message, NAMESIZE+BUFSIZE-1); if(str_len == -1) return 1; name_message[str_len] = 0; fputs(name_message, stdout); } } #define BUFSIZE 100 #define NAMESIZE 20 void *send_message(void *arg); void *recv_message(void *arg); void error_handling(char * message); char name[NAMESIZE]="[Default]"; char message[BUFSIZE]; int main(int argc, char **argv){ int sock; struct sockaddr_in serv_addr; pthread_t snd_thread, rcv_thread; void *thread_result; . . . . sock=socket(PF_INET, SOCK_STREAM, 0); . . . . pthread_create(&snd_thread, NULL, send_message, (void*)sock); pthread_create(&rcv_thread, NULL, recv_message, (void*)sock); pthread_join(snd_thread, &thread_result); pthread_join(rcv_thread, &thread_result); close(sock); return 0; }
쓰레드 기반 서버 구현[3] • chat_server.c & chat_client.c 실행결과 server Client MR.Lee Client Thomas
참고문헌 • “TCP/IP 소켓 프로그래밍”, 윤성우 저 • “운영체제(Understanding operating Systems)”, 김희철, 박영민, 이금석, 조병호, 최의인 공역