3. Philosophers

zinnnn37·2023년 2월 27일
0

42 SEOUL

목록 보기
6/9

3.0. Backgrounds

3.0.0. Program

  • 어떤 작업을 위해 실행할 수 있는 파일
    -> 파일이 저장 장치에만 저장되어 있고, 메모리에는 올라가 있지 않은 정적인 상태
    -> 즉, 아직 실행되지 않은 파일

  • 모든 프로그램은 운영체제가 해당 프로그램을 위한 메모리 공간을 할당한 뒤, 자원을 할당해주어야 실행할 수 있다.


3.0.1. Process

  • 운영체제로부터 자원을 할당 받은 작업의 단위를 의미한다.
    -> 즉, 프로그램이 실행된 상태
  • 프로그램을 실행하는 순간, 해당 파일은 운영체제가 할당해준 메모리에 올라가게 되며, 운영체제로붜 시스템 자원을 할당받는다.
  • 이 때, 운영체제는 각 프로세스의 독립된 메모리 영역을 code, data, stack, heap의 형식으로 할당한다.
  • 프로세스는 기본적으로 최소 하나의 스레드(메인 스레드)를 가진다.
  • 각 프로세스는 서로 다른 주소 공간에서 실행되며, 별도의 방식을 이용하지 않는 이상 한 프로세스는 다른 프로세스의 변수나 자료구조에 접근할 수 없다.

  • 일반적으로 하나의 프로그램은 여러 개의 프로세스로 구성된다.
  • 이 때 CPU는 각각의 프로세스를 동시에 처리하지 않고, 프로세스1을 어느 정도 처리하고 저장한 후, 프로세스2를 어느정도 처리하고 저장하는 방식으로 진행한다.
    -> 이렇게 여러 프로세스를 왔다갔다 하는 과정을 컨텍스트 스위칭(Context Switching)이라 한다.
  • 이러한 프로세스 간 이동이 많아지는 경우 CPU의 부담이 늘어나게 된다.
    -> 이를 해결하기 위한 것이 멀티 스레드이다.

→ Multi Process

  • 하나의 프로그램을 여러 개의 프로세스로 구성하여 각 프로세스가 하나의 작업을 처리하는 것을 의미한다.
  1. PROS: 하나의 프로세스에 문제가 생겨도 전체적인 프로그램은 동작한다.
  2. CONS: 프로세스 끼리 공유하는 메모리가 없기 때문에 Context Switching에 많은 비용이 발생한다.

3.0.2. Thread

  • 프로세스가 할당받은 자원을 이용하는 실행 단위
  • 한 프로세스 내에서 ㅓ여러 개의 스레드가 동시에 실행될 수 있다.
  • 프로세스 내의 스레드는 각각의 stack을 따로 할당받으며, code, data, heap 영역을 공유한다.
    -> 따라서 어떤 스레드가 프로세스 자원을 변경하면 이웃 스레드들이 변경 결과를 볼 수 있다.
  • 스레드는 프로세스가 할당 받은 메모리 영역 내에서, 스레드끼리 code, data, heap 영역을 공유하고, stack 영역은 별도로 관리한다.
  • 기본적으로 하나의 프로세스가 생성되면 하나의 스레드가 같이 생성되며 이를 메인 스레드라 부른다.
    -> 스레드를 추가로 생성하지 않는 한, 모든 프로그램 코드는 메인 스레드에서 실행된다.
  • 하나의 프로세스는 메인 스레드 외에 다른 여러개의 스레드를 가질 수 있고, 이를 멀티 스레드라고 한다.

→ Process vs Thread

  • 각각의 프로세스는 독립적이다.
    -> 한 프로세스의 종료는 다른 프로세스에 영향을 미치지 않는다.
  • 그러나 한 스레드의 종료는 해당 프로세스에 영향을 미친다
    -> 스레드는 code, data, heap 영역을 공유하기 때문에 어떤 스레드 하나라도 오류가 발생한다면 같은 프로세스 내의 다른 스레드까지 모두 강제로 종료된다.

→ Multi Thread

  • 하나의 프로그램을 여러 개의 스레드로 구성하고, 각 스레드가 하느이 작업을 처리하는 것
  1. PROS
    : 자원의 효율성이 증가한다
    -> 많은 프로세스를 생성함으로써 각 프로세스에 자원을 할당하는 작업(시스템 콜)이 줄어든다
    : Context Switching 처리 비용이 감소한다
    -> 스레드는 프로세스 메모리의 code, data, heap 영역을 공유하므로, Context Switching이 일어날 때 stack 영역만 처리하면 된다.
    : 응답 속도가 향상된다
    -> 스레드는 프로세스 메모리의 code, data, heap 영역을 공유하므로 프로세스 간 통신(IPC)보다 스레드 간 통신 응답 속도가 빠르다.
  2. CONS
    : 스레드 하나에 문제가 생기면 모든 프로세스가 종료된다
    : 자원을 공유하기 때문에 필연적으로 동기화 문제가 발생한다

→ 동기화 문제(Synchronization Issue)

  • 여러 스레드가 공유 변수를 함께 사용할 경우 발생할 수 있는 문제
    -> 스레드1이 어떤 공유 변수를 사용하다가 스레드2로 제어권이 넘어간다.
    -> 스레드2가 해당 공유 변수를 수정한 뒤 다시 스레드1로 제어권이 넘어간다.
    -> 스레드1은 바뀐 공유 변수로 인해 오류가 발생할 수 있다.

  • 스레드의 스케쥴링은 운영체제가 관리하지 않기 때문에, 설계할 때 주의하여 동기화 문제를 해결해야 한다.

→ Thread Safe

  • 멀티 스레드 프로그래밍에서, 어떤 공유 자원에 여러 스레드가 동시에 접근해도 프로그램 실행에 문제가 없는 상태를 의미
  • Thread Safe를 지키기 위한 방법은 네가지로 이루어진다
    1.Mutual Exclusion(상호 배제)
    : 공유 자원에 하나의 스레드만 접근할 수 있도록 세마포어/뮤텍스로 락을 통제하는 방법
    2. Atomic operation(원자 연산)
    : 공유 자원에 원자적으로 접근하는 방법
    -> 공유 자원 변경에 필요한 연산을 원자적으로 분리한 뒤, 실제로 데이터의 변경이 이루어지는 시점에 lock을 걸고, 데이터를 변경하는 시간 동안 다른 스레드의 접근이 불가능하도록 하는 방법
    3. Thread-local storage(스레드 지역 저장소)
    : 공유 자원의 사용을 최대한 줄이고, 각긱의 스레드에서만 접근 가능한 저장소를 사용함으로써 동시 접근을 막는 방법
    : 일반적으로 공유 상태를 피할 수 없을 때 사용하는 방식
    4. Re-entrancy(재진입성)
    : 스레드 호출과 상관 없이 프로그램에 문제가 없도록 작성하는 방법

3.0.3. Mutex

  • MUTual EXclusion(상호 배제)의 줄임말로, 동시 프로그래밍에서 공유 불가능한 자원의 동시 사용을 피하기 위해 사용한다.
  • 데이터 영역의 보호는 critical section(임계 영역)을 만들고, 임계 영역 내에 단 하나의 스레드만이 진입 가능하도록 하는 방식이다.
    -> key에 해당하는 어떤 객체(object)가 있으며, 이 객체를 소유한 스레드/프로세스만이 공유 자원에 접근할 수 있다.
  • 다중 프로세스들이 공유 리소스에 대한 접근을 조율하기 위해 동기화(Synchronization) 또는 락(lock)을 사용한다.
    -> 즉, 뮤텍스 객체를 두 스레드가 동시에 사용할 수 없다.

3.1. Allowed functions

3.1.0. pthread_create()

	#include <pthread.h>
	int	pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
  • 새로운 스레드를 생성하는 함수
  • thread 생성 성공 시 0을 리턴하고 실패 시 에러 코드를 리터한다.
    -> EAGAIN: 스레드 생성을 위한 자원이 부족하거나, PTHREAD_THREADS_MAX를 초과하여 스레드 생성을 요청하는 경우
  1. thread: 이곳에 스레드의 식별자가 저장됨
  2. attr: 스레드와 관련된 특성을 지정하기 위한 용도로 사용됨
  3. start_routine: 새로운 스레드는 arg를 매개변수로 하는 start_routine 함수를 실행시키면서 생성된다. 생성된 스레드는 pthread_exit(3)을 호출하거나 함수 내에서 return하는 경우 제거된다.
  4. arg: start_routine 함수의 매개변수로 들어간다. 분기될 함수 내에서 원하는 자료형을 캐스팅해야 한다.

3.1.1. pthread_join()

	#include <pthread.h>
    int	pthread_join(pthread_t thread, void **thread_return);
  • 지정된 스레드가 종료될 때까지 기다리는 함수로, 스레드의 종료는 pthread_exit(3)으로 종료되거나 return되는 경우 발생한다.
  • 스레드가 종료되는 경우 자원을 해제한다.
    -> 스레드는 종료된다고 모든 자원이 해제되는 것이 아니기 때문에 반드시 pthread_join()으로 자원을 해제해야 메모리 누수가 발생하지 않는다.
  • pthread_join()pthread_create()의 두 번째 매개변수인 attrjoinable(default) 상태의 스레드로 작동하고 detached 상태가 아니어야 사용 가능하다.
  • 함수 실행 성공 시 0, 실패 시 에러 코드를 반환한다.
    -> ESRCH: 식별번호 thread가 잘못된 식별번호인 경우
    -> EINVAL: 식별번호 threaddetached 상태인 경우
  1. thread: 스레드의 TID
  2. thread_return: 스레드 함수에 리턴 값이 있다면 이 변수로 전달된다. 없다면 NULL을 입력한다.

3.1.2. pthread_detach()

	#include <pthread.h>
    int	pthread_detach(pthread_t thread);
  • 스레드 식별자 thread를 가지는 스레드를 메인 스레드에서 분리시킨다. 이는 thread를 가지는 스레드가 종료되는 즉시 스레드의 모든 메모리를 해제해 줄 것을 보증한다.
    -> detached 상태가 아닌 경우 pthread_join(3)을 호출하지 않는 한 메모리를 해제하지 않는다. 반드시 pthread_join()으로 메모리를 해제할 것.
  • pthread_detach()를 호출하는 것 외에도 pthread_create()pthread_attr_tdetachstate를 지정해줌으로써 detached 상태로 스레드를 생성할 수도 있다.
    -> pthread_attr_tpthread_attr_int(3) 함수를 통해 변경할 수 있다.
  • 성공 시 0, 실패 시 에러 코드 반환
    -> ESRCH: thread 식별자를 가진 스레드가 존재하지 않는다.
    -> EINVAL: thread 식별자를 가진 스레드가 이미 detached 상태에 있다.

3.1.3. pthread_mutex_init()

	#include <pthread.h>
    int	pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutex_attr *attr);
  • 뮤텍스 객체초기화시키기 위해서 사용한다.
  • 초기화에 성공하면 잠금이 해제된다.
  • 주로 fast, recursive, error checking 3가지 중 하나를 선택할 수 있으며, 기본적으로는 fast가 사용된다.
  • 성공 시 0, 실패 시 에러 코드 반환
    -> EINVAL: 뮤텍스가 제대로 초기화되지 않은 경우
    -> EDEADLK: 뮤텍스가 이미 잠긴 경우(error checking 뮤텍스에서만 발생한다)
  1. mutex: 초기화시킬 뮤텍스 객체
  2. attr: 초기화 시킬 때 뮤텍스의 특징을 정의할 수 있는데, attr을 통해서 이루어진다. 기본 특징을 이용하고 싶다면 NULL을 입력한다.

3.1.4. pthread_mutex_destroy()

	#include <pthread.h>
    int	pthread_mutex_destroy(pthread_mutex_t *mutex);
  • 인자로 주어진 mutex가 가리키는 뮤텍스 객체를 제거한다.
  • 동적 할당 된 뮤텍스 객체인 경우 free()하기 전 pthread_mutex_destroy()를 먼저 호출 해야한다.
  • pthread_mutex_destroy()를 사용하기 위해서는 뮤텍스가 반드시 잠금이 해제된 상태여야 한다.
  • 성공 시 0, 실패 시 에러 코드 반환
    -> EINVAL: 지정한 뮤텍스 객체가 유효하지 않은 경우
    -> EBUSY: 지정한 뮤텍스 객체가 다른 스레드에 의해 잠겨있는 경우
  1. mutex: 제거할 뮤텍스 객체

3.1.5. pthread_mutex_lock()

	#include <pthread.h>
    int	pthread_mutex_lock(pthread_mutex_t *mutex);
  • 뮤텍스의 잠금을 요청한다.
  • 만약 잠금되어 있지 않은 상태일 경우 잠그고, 잠긴 상태라면 해당 뮤텍스의 잠금이 해제될 때까지 대기한다(다른 스레드가 이미 임계 영역에 진입한 경우)</ span>
  • 성공 시 0, 실패 시 에러 코드 반환
    -> EINVAL: 뮤텍스가 잘못 초기화된 경우
    -> EDEADLK: 이미 잠금을 얻은 스레드가 다시 잠금을 요청할 때(error checking 뮤텍스인 경우 사용 가능)

3.1.6. pthread_mutex_unlock()

	#include <pthread.h>
    int	pthread_mutex_unlock(pthread_mutex_t *mutex);
  • 뮤텍스 잠금을 해제한다.
  • fast의 경우 언제나 잠금을 해제한다.
  • recursive의 경우 잠겨있는 뮤텍스의 수를 감소시키고, 이 수가 0이 된다면 잠금을 해제한다.
  • 성공 시 0, 실패 시 에러 코드 반환
    -> EINVAL: 지정한 뮤텍스 객체 mutexNULL이거나 유효한 객체가 아닌 경우
    -> EPERM: 지정한 뮤텍스 객체가 호출한 스레드에 의해 잠기지 않았을 경우

3.1.7. gettimeofday()

	#include <sys/time.h>
    int	gettimeofday(struct timeval *tv, struct timezone *tz);
  • 1970-01-01 00:00:00 +0000(UTC) 이후의 현재가지 경과된 초와 micro초(백만분의 1초) 값을 얻는 함수.
  • tz는 사용되지 않는다.
  • 함수 성공 시 0, 실패 시 에러코드 반환
    -> EFAULT: tv 또는 tz의 메모리 영역이 유효하지 않은 경우
  1. tv
	struct timeval
    {
    	time_t		tv_sec;		// seconds(long)
        suseconds_t	tv_usec;	// microseconds(int)
    }

ref
program, process, thread https://wooono.tistory.com/522
create https://www.joinc.co.kr/w/man/3/pthread_create
join https://www.joinc.co.kr/w/man/3/pthread_join
detach https://www.joinc.co.kr/w/man/3/pthread_detach

mutex_init https://www.joinc.co.kr/w/man/3/pthread_mutex_init
lock, unlock https://www.joinc.co.kr/w/man/3/pthread_mutex_lock

gettimeofday https://www.it-note.kr/130

0개의 댓글