반응형

17-1. 프로세스(Process)와 쓰레드(Thread)

쓰레드란 무엇인가?

경량화된 프로세스 

동시 실행 가능.


프로세스와 차이점

스택, PC를 포함한 레지스터 복사본, 쓰레드 지역 보관영역을 제외한 나머지 메모리 영역을 공유.

※ fork를 통해 프로세스 복사했다면 부모와 자식 프로세스는 서로 독립적으로 작동한다.

data : 전역변수가 저장되는 공간.

heap : 동적할당을 하는 메모리 공간.

stack : 지역변수를 위해 사용하는 공간.

보다 간단한 컨텍스트 스위칭.

일부 메모리를 공유하므로 쓰레드간 통신이 편리.

(프로그래밍 하는데 부담이 될 수 있음.)


17-2. 쓰레드 생성하기

쓰레드란 무엇인가?

프로세스(LINUX) : 실행되고 있는 일의 단위

쓰레드 : 프로세스 내에서 다시 나누어지는 일의 단위. 프로세스가 존재해야 쓰레드 생성이 가능.


#include<pthread.h>

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

쓰레드를 생성한다. nonblocking함수.

리턴 : 성공시 0, 실패시 이외 값.

thread : 생성된 쓰레드의 ID를 저장할 변수의 포인터.

attr : 생성하고자 하는 쓰레드의 특성. 일반적으로 NULL을 전달.

start_routine : 리턴타임이 void*이고 인자도 void*인 함수를 가리키는 포인터

arg : 쓰레드에 의해 호출되는 함수에(start_routine 포인터가 가리키는 함수) 전달하고자 하는 인자 값.


※ main쓰레드가 먼저 죽으면 자식 쓰레드는 끝까지 실행되지 않고 종료될 수 있다.


#include<pthread.h>

int pthread_join(pthread_t th, void **thread_return);

리턴 : 성공시 0, 실패시 이외 값.

th : 인자로 들어오는 ID의 쓰레드가 종료할 때까지 실행을 지연시킨다. blocking 함수.

thread_return : 쓰레드가 종료 시 반환하는 값에 접근할 수 있는 2차원 포인터.


임계영역과 쓰레드에 안전한 함수

임계영역 : 두 개 이상의 쓰레드에 의해서 동시에 실행되면 안 되는 영역.

쓰레드 안전한 함수(Thread safe function) : 임계영역에서 호출이 가능한 함수.

예) gethostbyname(쓰레드 불안전한 함수) vs gethostbyname_r(쓰레드 안전한 함수)


gcc -D_REENTRANT thread2.c -o thread2 -lpthread

-D_REENTRANT : #define _REENTRANT의 뜻. 쓰레드 불안전한 함수를 쓰레드 안전한 함수로 바꿔주는 매크로.

-lpthread : 기본적으로 쓰레드와 링크되어 있지 않기 때문에 링크시키기 위한 옵션.


17-3. 임계영역 & 쓰레드의 문제점
컴퓨터가 덧셈을 하는 기본 원리

※ 임시메모리 = accumulator

두 개의 쓰레드에 의한 덧셈 연산
int i = 10;    // 전역 변수
....
i+=10;

※ A Thread가 끝나기 전에 B Thread가 수행될 수 있다.

※ 3단계에서 i=30; 이 되어야 하지만 i=20;이 되어 버렸다.


17-4. 동기화(Synchronization)

임계영역 : 둘 이상의 쓰레드에 의해서 공유되는 메모리 공간에 접근하는 코드 영역.

동시에 접근 가능한 메모리(전역변수)에 접근해서 연산하는 코드 영역

동기화

1. 공유된 메모리에 둘 이상의 쓰레드가 동시 접근하는 것을 막는 행위.

2. 둘 이상의 쓰레드 실행순서를 컨트롤(control)하는 행위.

동기화 기법 : 뮤텍스, 세마포어


17-5. 뮤텍스(Mutex)

뮤텍스의 정의

Mutual Exclusion의 줄이말로 쓰레드 서로 간에 동시 접근을 허용하지 않겠다는 의미.

pthread_mutext_t 타입의 변수를 가리켜 뮤텍스라고 한다.

일종의 (화장실) 문고리에 해당.

뮤텍스의 기본 원리

임계영역에 들어갈 때 뮤텍스(문고리)를 잠그고 들어간다.

임계영역에서 나올 때 뮤텍스를 풀고 나간다.

뮤텍스를 조작하는 함수들

#include<pthread.h>

초기화 : pthread_mutex_init

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);

뮤텍스 소멸 : pthread_mutex_destroy

int pthread_mutex_destroy(pthread_mutex_t *mutex);

뮤텍스 잠금 : pthread_mutex_lock

int pthread_mutex_lock(pthread_mutex_t *mutex);

뮤텍스 풀기 : pthrea_mutex_unlock

int pthread_mutex_unlock(pthread_mutex_t *mutex);


뮤텍스 동기화 원리


17-6. 세마포어(Semaphore)

세마포어의 정의

sem_t 타입의 변수를 가리켜 흔히 세마포어라 표현한다.

세마포어의 기본 원리

세마포어는 정수를 지닌다. 정수의 값이 0이면 실행 불가를 의미한다.

세마포어가 1이상이 되면 실행 가능을 의미한다.

세마포어는 0미만이 될 수 없지만 1이상은 될 수 있다.

바이너리 세마포어 : 0과 1의 상태만을 지닌다.

세마포어를 조작하는 함수들

#include<semaphore.h>

세마포어 초기화 : sem_init

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

value : 초기화하고자 하는 값.

세마포어 소멸 : sem_destroy

int sem_destroy(sem_t * sem);

세마포어 감소 : sem_wait

int sem_wait(sem_t * sem);

세마포어 증가 : sem_post

int sem_post(sem_t * sem);


세마포어 동기화 원리

Thread A는 새로운 데이터를 주고 Thread B는 그 데이터를 받는다.

a->b->a->b 순서가 반복된다.


17-7. 쓰레드 기반 서버 구현하기

채팅 서버의 임무 : 전체 클라이언트에게 메세지 내용을 다 보내야 되기 때문에 클라이언트 소켓을 갱신해야 한다.


pthread_mutex_lock(&mutx);

for(i=0;i<clnt_number;i++)

    write(clnt_socks[i], message, len);

pthread_mutex_unlock(&mutx);

위 소스에서 clnt_socks[i]에서 클라이언트 소켓 파일 디스크립터가 존재한다고 생각하고 write함수를 호출하지만 파일 디스크립터가 사라질 위험이 있기 때문에 mutex를 통해 잠가 놓는다.



반응형
반응형

16-1. 스트림의 분리

입출력 스트림의 분리 

입력을 위한 파일포인터와 출력을 위한 파일 포인터를 독립적으로 유지.

스트림을 분리하는 이유

프로그래밍을 단순하게 한다.

입출력 겸용 스트림의 경우 입출력 작업 전환시 버퍼를 비워야 한다.

일반적인 프로그래밍 방식.

주의할 사항

출력 스트림 종료시 EOF 전송이 된다.

문제점 : fclose함수를 통해 출력 스트림 뿐만 아니라 입력 스트림까지 완전 종료가 발생할 수 있다.

발생 상황 : 디스크립터가 하나고 그걸 통해 입출력(2개)용 FILE포인터를 만들 때 발생.


16-2. 파일 디스트립터의 복사와 스트림의 분리

파일 포인터(FILE*)를 이용한 소켓의 종료과정

fclose 함수의 호출은 파일 디스크립터의 종료로 이어지고, 이는 소켓의 완전종료를 의미한다.


※ 하나의 파일 디스크립터를 기반으로 입출력 파일 포인터 생성시 하나의 파일 포인터만 종료되어도 완전 종료된다.

※ 시스템 리소스(여기서는 소켓)는 가리키는 포인터(여기서는 파일 디스크립터)가 없을 때 종료된다.


하나의 파일 디스크립터를 기반으로 하나의 파일포인터만 생성

파일 디스크립터가 두 개일 경우에는 두 개의 파일 디스크립터가 종료되어야 완전 종료된다.


파일 디스크립터 복사

하나의 파일(혹은 소켓)에 접근할 수 있는 파일 디스크립터를 하나 더 만들어 내는 것.


#include<unistd.h>

int dup(int fildes);

int dup2(int fildes, int fildes2);

리턴 : 성공시 복사된 파일 디스크립터(번호), 실패시 -1

fildes : 복사하고자 원하는 파일 디스크립터

fildes2 : 복사되는 파일 디스크립터의 값을 명시적으로 지정하고 싶을 때 사용.


※ 쓰기 파일 디스크립터를 half close 함으로서 EOF가 전송됨.

fflush(sock);

shutdown(fileno(wstrm), SHUT_WR);    //  1차 종료, EOF 메세지 전송

fclose(wstrm);

반응형
반응형

15-1. 표준 입출력 함수의 장점

표준 입출력 함수의 장점

이식성이 좋아진다.

 - 모든 시스템은 ANSI 표준 C를 지원한다.

효율성을 높일 수 있다.

 - 표준 입출력 함수에 의한 버퍼를 제공받는다. (표준 입출력버퍼, 소켓 버퍼)

 - 소켓 생성 버퍼 목적 :  프로토콜의 완성을 위한 것.

 - 표준 입출력 버퍼 목적 : 성능 향상


파일 포인터와 파일 디스크립터

파일 포인터

 - 표준 입출력 함수 호출을 위해서 필요한 파일 포인터. = FILE 구조체의 포인터.

파일 디스크립터를 이용한 시스템 입출력

 - 시스템 입출력 함수 사용 시 필요

 - 정수값이다.

※ 소켓 기반의 표준 입출력 함수 사용을 위해 파일 디스크립터를 기반으로 파일 포인터를 생성.


15-2. 표준 입출력 함수의 사용

파일 디스크립터 -> 파일 포인터

#include<stdio.h>

FILE * fdopen(int files, const char *mode);

리턴 : 성공시 FILE 포인터, 실패시 NULL


fdopen 후 파일 디스크립터와 파일 포인터 상태


※ 표준 입출력 함수를 사용할 경우 : 파일 버퍼 -> 소켓 버퍼 -> 실제 전송 혹은 수신.


파일 포인터 -> 파일 디스크립터

#include<stdio.h>

int fileno(FILE *stream);

리턴 : 파일 포인터가 가리키는 파일의 디스크립터 리턴.

stream : 파일 포인터. 파일 디스크립터는 파일 모드와는 상관이 없다.


15-3. 소켓 기반의 표준 입출력 함수의 사용

파일에 버퍼가 필요한 이유

CPU가 직접 I/O를 하면 비효율적이기 때문에 버퍼에 그 내용을 저장했다가 파일로 전송한다.

flag를 두어 CPU가 여유 있을 때 system call을 통해 버퍼 내용을 파일로 보낸다.

system call을 이용하면 모아서 입출력 하는 것이 힘들다.

하지만 파일 버퍼를 이용하면 데이터를 모아서 입출력하는 데 좋아진다.

반응형

+ Recent posts