네트워크 프로그래밍 18) 스레드

zh025700·2022년 5월 25일
0

네트워크 프로그래밍

시험 정리용... 내용 매우 알아보기 힘들 것임...

18. 멀티스레드 기반의 서버 구현

쓰레드 이론

스레드 등장 배경

  • 프로세스 생성은 OS에서 부담되는 작업이다

멀티프로세스의 단점

  • 프로세스 생성이라는 부담..
  • 프로세스 사이 데이터 교환을 위해 별도의 IPC 기법 적용
  • context switching 오버헤드가 매우 큼

=> 스레드 나옴

장점

  • 스레드 생성, context switching은 프로세스보다 빠름
  • 스레드 사이 데이터 교환은 특별한 기법 사용 X
  • 스레드는 메모리를 공유해 IPC가 필요없음

쓰레드, 프로세스 차이점

  • 프로세스는 데이터, 스택, 힙 영역을 별도로 유지

    • 프로세스는 독립적이다

스레드는 스택 영역만 분리!!

  • 스레드는 스택만 분리고 힙,데이터 영역은 공유한다

    • 지역 변수 분리!
    • 전역, static, 동적할당되는 것은 분리 X!
  • 스레드끼리 context switching 오버헤드가 낮다

  • 데이터 영역 공유 덕분에 data change가 쉽다

    그래서..

  • 프로세스: OS 관점에서 별도의 실행흐름을 구성

  • 쓰레드: 프로세스 관점에서 별도의 실행흐름을 구성

OS는 여러 프로세스를 지니고 프로세스는 여러 쓰레드를 지닌다

스레드 생성, 실행

스레드의 생성과 흐름의 구성

스레드는 스레드만의 main을 정의해야함.
그리고 이를 형성해 줄 것을 요청해야함.
#include <pthrad.h>

int pthread_create(pthread_t *restrict thread, const pthread_attr_t * restrict attr, void * (*start_routine)(void*),void* restring arg);
  • 성공 시 0, 실패 시 0 이외의 값 반환
  • thread: 생성할 스레드 ID 저장할 주소 값
  • attr: 스레드의 특성, NULL일 시 기본 스레드
  • start_routine: 스레드의 main 함수의 주소 값
  • 세번째 함수에 전달할 인자의 정보를 담는 변수의 주소 값

스레드가 생성된 main함수가 종료되면 프로세스 전체가 소멸되어 해당 스레드도 죽는다!
그래서 따로 함수를 이용해 확인을 해야함!!

  • 스레드의 메인 함수가 다 종료되기 전에 프로그램의 메인이 종료되어 프로그램이 전체 역할을 못 할수 도 있기 때문이다
#include <pthread.h>

int pthread_join(pthread_t thread, void **status);
  • 성공 시 0, 실패시 0 이외
  • thread: 해당 스레드가 종료될 때 까지
  • status: 해당 스레드의 main이 반환하는 값이 저장될 변수의 주소 값

=> 즉, 전달되는 ID의 스레드가 종료될 때 까지 이 함수를 호출한 프로세스(스레드)를 대기상태로

join을 호출하고 난 이후! 해당 프로세스는 해당 스레드가 끝날 때까지 기다림!!

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

  • 둘 이상의 쓰레드가 동시에 실행하면 문제를 일으키는 함수

    • 임계영역(Critical section)의 문장이 하나 이상 존재하는 함수

  • thread-safe
    • 둘 이상 쓰레드에 의해 동시 실행되어도 문제 X
  • thread-unsafe
    • 둘 이상 쓰레드에 의해 동시 실행되면 문제 발생

쓰레드에 안전한 함수도 임계영역이 존재 할 수도

엥?

  • 기본적으로 제공되는 대부분의 표준 함수들은 스레드에 안전한 조치가 되어있음
    • _REENTRANT 매크로를 이용해 자동으로 안전한 함수로 변환이됨.

쓰레드 관련 코드가 있으면 D_REENTRANT를 컴파일할때 삽입

워커 스레드 모델

두 쓰레드가 하나의 전역 변수에 접근을 하면??

예상 결과와 실행 결과가 다름!!

왜??

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

하나의 변수에 둘 이상의 쓰레드가 동시에 접근하면 문제가 생김

스레드들은 CPU를 RR 방식으로 사용하니 동시접근이 될까??

동시 접근이 아니라.. 값에 접근할 때 동기화가 안된 값을 가져가는 문제가 생길 수 있음..

  • 연산을 진행한 값을 다른 스레드가 사용해야하는데, 연산이 진행되지 않은 값을 가져가버리는 경우...

    동기화가 필요!!

임계영역

함수 내에 둘 이상의 스레드가 동시 실행하면 문제를 일으키는 코드 블록
  • 동일한 메모리 공간에 둘 이상이 접근할 떄 !!! 문제가 생기는 영역?!!!?!?
    • 즉 동기화에 문제가 생기는 영역

쓰레드 동기화

동기화의 두가지 측면

동기화가 필요한 상황

  1. 동일한 메모리 영역으로 동시 접근이 발생
  2. 동일한 메모리 영역에 접근하는 스레드의 실행 순서를 지정해야하는 상황
    • A스레드가 가공한 값을 B가 사용해야하는 경우 A->B로 실행 순서가 되어야..
  • 동기화를 통해 동일 메모리의 스레드의 동시 접근을 막을 수 있다, 접근 순서를 정함으로써
  • mutex based
  • semaphore based

동기화는 운영체제의 종속적이라 다른 운영체제는 다른 접근 방법을 사용해야한다

Mutex

  • 쓰레드의 동시 접근을 허용하지 않는다.

생성, 소멸

#include <pthread.h>

int pthread_mutex_init(*mutex,*attr);
int pthread_mutex_destroy(*mutex);
  • 성공시 0 실패시 0아닌 값
  • mutex
    • 생성: 뮤텍스의 참조 값을 저장할 변수의 주소값
    • 소멸: 없앨 뮤텍스 저장하는 변수의 주소값
  • attr: 생성하는 뮤텍스의 특성 정보를 담고 있는 변수의 주소 값, 특성 지정 안 할 경우 NULL
뮤텍스의 생성을 위해선 뮤텍스형 변수가 하나 선언되어야함.
pthread_mutex_t mutex;

잠금, 품

$include <pthread.h>

int pthread_mutex_lock(pthread_mutext_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
  • 성공시 0 실패시 0 이외

lock과 unlock을 이용해 임계영역의 시작과 끝을 감싸!

  • 데드락이 발생하지 않게 lock 호출 뒤 unlock을 호출 해야함.
  • 성능을 위해 lock과 unlock의 호출 수를 적게!!

Semaphore

생성, 소멸

#include <semaphore.h>

int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_destroy(sem_t *sem);
  • 성공시 0 , 실패시 0 이외의 값
  • sem
    • 생성: 세마포어 값 저장을 위한 변수의 주소 값
    • 소멸: 소멸하고자하는 세마포어 값 저장하는 변수의 주소 값
  • pshard
    • 0 이외의 값: 둘 이상 프로세스에 접근 가능한 세마포어 생성
    • 0: 하나의 프로세스 내에서만 접근 가능한 세마포어 생성
      • 동기화가 목적임으로 이거 사용
  • value: 생성되는 세마포어의 초기 값

lock unlock

#include <semaphore.h>

int sem_post(sem_t *sem);
int sem_wait(sem_t *sem);
  • 성공시 0 실패시 0 이외
  • sem: 세마포어 변수 주소 값
    • post전달: 세마포어 값 1 증가
    • wait 전달: 세마포어 값 1 감소

세마포어 활용법

  • init을 호출하면 세마포어 오브젝트가 만들어짐.

    • 여기엔 세마포어 값이라는 정수 하나가 기록,
  • sem_post가 호출되면 세마포어 값 1 증가

  • sem_wait이 호출되면 세마포어 값 1 감소
    세마포어값은 0보다 작아질 수 없음!!!

  • 그래서 값이 0인 세마포어에 sem_wait을 호출하면??

    • 블로킹 상태가 됨

이를 이용해 임계영역을 동기화

sem_wait(&sem) // 세마포어 값 0
//임계
//끝
sem_post(&sem) //세마포어 값 1

이렇게 되면, sem_post를 호출 전에는 다른 쓰레드에 의해 임계영역의 진입이 허용 안됨.

코드를 보고 이해를 하자!! 필요한 것만 구성

int main(int argc,char* argv[]){
	pthread_t id_t1,id_t2;
	sem_init(&sem_one,0,0);
	sem_init(&sem_two,0,1);

	pthread_create(&id_t1,NULL,read,NULL);
	pthread_create(&id_t2,NULL,accu,NULL);

}

void* read(void* arg){
	int i;
	for(i=0;i<5;i++){
		fputs("Input num: ",stdout);
		sem_wait(&sem_two);
		scanf("%d",&num);
		sem_post(&sem_one);
	}
	return NULL;
}

void* accu(void* arg){
	int sum=0,i;
	for(i=0;i<5;i++){
		sem_wait(&sem_one);
		sum+=num;
		sem_post(&sem_two);
	}
	printf("Result: %d \n",sum);
	return NULL;
}

값이 0일때 wait이면 블로킹을 이용!!

  1. sem_one은 세마포어 값 0으로 시작, sem_two는 1로 시작.

  2. 스레드를 생성하고 실행

  3. accu할때 값이 입력도 안됐는데 가져가는 경우를 막기 위해 초기 sem_one을 0으로 설정

    • sem_wait을 통해 block해 놓음
      • 입력을 했으면 sem_one 값이 1 증가했으니 진입 가능해짐 그래서 진입
    • 이후 값을 입력했으면 sem_post를 통해 sem_one을 증가 시켜 accu 진행
  4. 사용자가 값을 입력해 sem_one값이 1로 변해 accu가 값을 가져감, 이후 two를 1로 post해 read

  • two가 1이고 one은 0 이니 블록킹이 안일어난 read 함수가 먼저 실행됨
    • 값을 입력하면 연산을 진행할 수 있음

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

스레드 소멸 방법

  1. pthread_join 호출
    • 종료 대기 뿐만 아니라 스레드의 소멸도 해줌, but 블로킹 상태가 되어버림
  2. pthread_detach 호출
    • 일반적으로 이거 사용
#include <pthread.h>

int pthread_detach(pthread_t thread);
  • 성공시 0 실패시 0 이외
  • thread: 종료와 동시에 소멸시킬 스레드의 ID 값
  • 실행이 끝난 스레드만 종료가 됨!!
  • detach 호출을 하면 join 호출 불가
  • 종료되지 않은 쓰레드가 종료되거나 블로킹 상태에 놓이지는 않는다
    • 이를 이용해 쓰레드에 할당된 메모리의 소멸을 유도할 수 있다

다중 접속 서버

clnt_cnt와 clnt_socks에 접근하는 코드는 임계영역을 구성

  • 클라이언트가 새로 추가, 삭제되면 위의 값에는 동시에 변화가 생김
  • 그래서 데이터는 동기화가 필요
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_detach(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[BUF_SIZE];

	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){
				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_mutex_unlock(&mutx);
}
profile
정리

0개의 댓글