Search
Duplicate

Philosopheres 고민하기, 참고

간단소개
philosophers에서 배워야할 기초 cs, 과제 팁
팔만코딩경 컨트리뷰터
ContributorNotionAccount
주제 / 분류
42seoul
42cursus
Thread
C
Scrap
태그
philosopheres
9 more properties
노션이 보기 편합니다 Philosopheres

Philosopheres

<쓰레드설명, 쓰레드동기화 방법의 종류>
<뮤텍스와 세마포어의 차이>
<pthread>
<우테코 뮤텍스 세마포어 설명>

개발 전 기록사항

 메모

매개변수 처리 후에 다 통과하면 과제 수행하면 되겠군
pthread를 써야하는군
mutex가 뭐여

테스트 및 구현순서

1.
쓰레드 테스트
2.
mutex 할때와 안할때 차이 보기
a.
변수 a를 1씩 더하는 로직 1000만큼 돌리면서 값 확인해보기
3.
매개변수 처리하기

 process, thread, mutex

 process

실행중인 프로그램
프로그램이 운영체제에 의해 메모리 공간을 할당받아 실행중인 것
생성 시 많은 시간과 자원이 소비됨

 thread

쓰레드란

프로세스 내에서 실행되는 여러 흐름의 단위
프로세스의 실행 단위

여러 프로세스 동시에 돌리면?

CPU의 프로세스 교환 과정에서 시간이 걸림 → 제 성능 발휘 여렵

쓰레드 장점

문맥교환 (context switching) 시간이 짧다
메모리 공유로 인하여 시스템 자원 소모 줄어듬
응답시간 단축

쓰레드마다 스택을 독립할당하는 이유

각각의 쓰레드는 위 그림과 같이 각자의 스택과 PC 레지스터 값을 가진다.
스택 메모리 공간이 독립적이라는 것은,
독립적인 함수 호출이 가능
독립적인 실행 흐름이 가능
을 뜻한다.

쓰레드마다 PC 레지스터를 독립할당하는 이유

PC 레지스터란, 쓰레드가 명령어의 어디까지 수행하였는지를 나타낸다.
쓰레드는 CPU를 할당받았다가 스케줄러에 의해 다시 선점당한다.
즉, 명령어를 연속적으로 수행하지 않고 중간중간 대기 시간이 존재한다는 것이다.
각 쓰레드가 어느 부분까지 수행했는지를 기억하기 위해 PC 레지스터를 독립할당한다.

쓰레드 동기화 방법의 종류

1.
Mutex
쓰레드의 동시접근을 허용하지 않음
2.
Semaphore
Mutex와 비슷하나, 동시접근 동기화가 아닌 접근 순서 동기화
3.
Monitor
기타 각 방법의 설명과 차이는 아래 블로그 참고

 pthread

POSIX Thread의 약자로, 유닉스 계열 POSIX시스템에서 병렬적으로 작동하는 소프트웨어를 작성하기 위해 제공하는 API. (쓰레드를 쉽게 만들기 위해 도와주는 API)

pthread_create

pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg); 특정 함수를 실행하는 쓰레드를 만드는 함수
1번 매개변수(pthread_t *thread)
쓰레드 식별자, 쓰레드 구분을 위한 id
2번 매개변수(const pthread_attr_t *attr)
쓰레드 특성을 지정하기 위해 이용하는 매개변수, 보통 NULL
3번 매개변수(void *(*start_routine)(void *))
thread가 실행되었을 때 시작할 쓰레드 함수
4번 매개변수(void *arg)
스레드가 분기할 함수에 보낼 입력 파라미터

pthread_join

int pthread_join(pthread_t th, void **thread_return) th쓰레드가 종료되길 기다렸다가, 쓰레드가 종료되면 다음 명령어 진행
1번 매개변수(pthread_t th)
쓰레드 식별자, 쓰레드 구분을 위한 id
2번 매개변수(void **thread_return)
파라미터 리턴값

pthread_detach()

int pthread_detach(pthread_t th); join과 달리 반환값 처리가 불가하다.
1번 매개변수(pthread_t th)
쓰레드 식별자, 쓰레드 구분을 위한 id

join vs detach

join → blocking function detach → non-blocking function
detach로 실행할 경우 main thread가 쓰레드 생성 후 약 1초 뒤 종료가 되기에, 결과를 의도할 수 없다.
따라서, join함수를 사용할 것 같다.
void *p_func(void *data) { pid_t pid; pthread_t tid; pid = getpid(); tid = pthread_self(); char *thread_name = (char *)data; int i = 0; while (i < 3) { printf("tn : %s, tid : %x, pid : %u\n", thread_name, (unsigned int)tid, (unsigned int)pid); i++; sleep(1); } return (0); } int main() { . . . . thr_id = pthread_create(&pthread[0], NULL, p_func, (void*)p1); //2 if(thr_id < 0) { perror("pthread0 create error"); exit(1); } thr_id = pthread_create(&pthread[1], NULL, p_func, (void *)p2); //2 if(thr_id < 0) { perror("pthread1 create error"); exit(1); } // pthread_join(pthread[0], (void **)&status); //6 // pthread_join(pthread[1], (void **)&status); pthread_detach(pthread[0]); pthread_detach(pthread[1]); sleep(1); printf("end??\n"); return 0; }
C
복사

mutex

pthread_mutex_init

pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutex_attr *attr)
1번 매개변수(pthread_mutex_t *mutex)
초기화시킬 mutex객체
2번 매개변수(const pthread_mutex_attr *attr)
mutex 특성 변경, 기본 시 NULL 우리는 attr 설정함수를 사용하지 못하므로 NULL 사용

pthread_mutex_destory

int pthread_mutex_destroy(pthread_mutex_t *mutex)
1번 매개변수(pthread_mutex_t *mutex)
삭제할 mutex 객체
자원을 되돌려준다.

pthread_mutex_lock

int pthread_mutex_lock(pthread_mutex_t *mutex)
1번 매개변수(pthread_mutex_t *mutex)
lock할 뮤텍스
해당 뮤텍스에 대해 잠금을 시도하는데, 이미 다른 쓰레드에 의해 잠겨있다면 잠금을 얻을 수 있을 때까지 무한대기.

pthread_mutex_unlock

int pthread_mutex_unlock(pthread_mutex_t *mutex);
1번 매개변수(pthread_mutex_t *mutex)
unlock할 뮤텍스

문제 해결

문제해결순서 (프로그램 순서)

main process

1.
매개변수 에러처리
2.
변수 및 값 초기화
3.
philo가 한명일 때 예외처리
4.
뮤택스를 사용하여 쓰레드 생성 동안 대기
5.
쓰레드 생성
6.
뮤텍스를 unlock하여 쓰레드 실행
7.
모니터 실행
8.
join을 통한 쓰레드 수거
9.
메모리, 뮤택스 해제

thread process

1.
쓰레드 생성 뮤택스 대기
2.
짝수번째일 경우 잠시 대기
3.
무한반복
a.
dieflag 확인
b.
홀수 번째 철학자는 왼오, 짝수번째 철학자는 오왼 순서로 포크 집기
c.
정해진 시간동안 먹기 (이 때, 먹은 시간 및 출력 순서가 중요)
d.
포크 내려놓기
e.
다먹었는지 확인
f.
sleep 시간만큼 대기
g.
think 출력
뮤택스 lock / unlock은 중요 프로세스를 제외하곤 미표기하였음

참고사항 및 최적화

데드락 확률 반으로 줄이기 (순서 차이 활용)

포크 집는 순서를 조정하여 데드락 확률을 반으로 줄일 수 있다.
홀수 번째 철학자와 짝수번째 철학자가 우선 집는 포크 방향을 다르게 함으로써 데드락 확률을 줄일 수 있다.
2명일 때를 생각해보면 쉽게 이해할 수 있다.

데드락 확률 줄이기 (시간 차이 활용)

홀수 번째 철학자와 짝수 번째 철학자의 시간 term을 활용하여 데드락 확률을 줄일 수 있다.
쓰레드를 먼저 만들었다고 해서 해당 쓰레드가 먼저 뮤택스를 선점할 수 있다고 단정할 수 없다.

오버헤드 줄이기

쓰레드 생성 후에도, 나머지 쓰레드의 생성까지 대기하여 다수의 철학자의 식사 시작 시간을 최대한 비슷하게 할 수 있다.
start_flag mutex를 통해 쓰레드 생성 후 대기할 수 있다.
뮤택스 또한 한 쓰레드가 unlock할 때 까지의 대기시간이 있지만, 쓰레드 생성보다 시간소요가 현저히 적다.
static void *philo_life_cycle(void *v) { t_philo *philo; philo = (t_philo *)v; check(pthread_mutex_lock(&philo->var->start_flag), MUTEX_LOCK); check(pthread_mutex_unlock(&philo->var->start_flag), MUTEX_UNLOCK); waiting(philo); while (1) { ... } return (0); } static int philosophere(int argc, char *argv[]) { ... check(pthread_mutex_lock(&var.start_flag), MUTEX_LOCK); while (i < var.philo_count) { check(pthread_create(&(var.philo[i].thread), 0, philo_life_cycle, &var.philo[i]), THREAD_CREATE); if (!var.philo[i++].thread) ft_error("thread create error"); } check(gettimeofday(&var.start_time, 0), GET_TIME); check(pthread_mutex_unlock(&var.start_flag), MUTEX_UNLOCK); ... }
C
복사

포크 독점 방지

sleep까지 수행한 철학자(쓰레드)가 양 옆의 철학자가 포크를 집기 전 다시 본인이 포크를 선점하는 것을 방지하면, 기아 발생을 현저히 줄일 수 있다.
sleep 후 think 출력 후 잠시동안 sleep한다면, 해당 쓰레드보다 양 옆의 쓰레드가 포크를 가져갈 확률이 높아진다.

sleep 쪼개기

sleep함수를 쪼개어 수시로 쓰레드가 깨어날 수 있게 한다.
(usleep(n)이 n초 쉰다고 가정하고) usleep(3)일 경우, 쓰레드가 3초 후 곧바로 실행을 개시하지 않는다.
* thread context switching