치춘짱베리굿나이스
[Rank 3] Philosophers - 스레드 함수 예 본문
아니 마크다운이 다 깨지네... 하느님 나에게 왜 이딴 시련을...
코드
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* test_create_join_detach.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: jiychoi <jiychoi@student.42seoul.kr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2021/08/03 15:02:52 by jiychoi #+# #+# */
/* Updated: 2021/08/03 16:43:38 by jiychoi ### ########.fr */
/* */
/* ************************************************************************** */
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
typedef struct s_thread_struct
{
pthread_t thread_id;
int index;
int value;
} t_thread_struct;
void *thread_function(void *param)
{
int index;
t_thread_struct *data;
index = 0;
data = (t_thread_struct *)param;
while (index < 10)
{
printf("%dth Thread %llu\t:\tindex %d\n",
data->index, (unsigned long long)data->thread_id, index++);
sleep(1);
}
return (0);
}
int main(void)
{
t_thread_struct threads[10];
int index;
int thread_err;
index = -1;
while (++index < 10)
{
threads[index].index = index;
thread_err = pthread_create(&threads[index].thread_id, NULL,
thread_function, (void *)&threads[index]);
printf("Thread index %d created\n", index);
if (thread_err < 0)
exit(0);
}
index = -1;
while (++index < 10)
{
pthread_join(threads[index].thread_id,
(void **)&(threads[index].value));
printf("Thread index %d joined\n", index);
//pthread_detach(threads[index].thread_id);
//printf("Thread index %d detached\n", index);
}
printf("All the threads call finished\n");
exit(0);
}
동작 방식
1. 스레드 생성
t_thread_struct threads[10];
int index;
int thread_err;
index = -1;
while (++index < 10)
{
threads[index].index = index;
thread_err = pthread_create(&threads[index].thread_id, NULL,
thread_function, (void *)&threads[index]);
printf("Thread index %d created\n", index);
if (thread_err < 0)
exit(0);
}
이때 t_thread_struct 구조체의 형태는 다음과 같음
typedef struct s_thread_struct
{
pthread_t thread_id;
int index;
int value;
} t_thread_struct;
- thread_id: 스레드의 id (pthread_create를 통해 생성된 스레드의 아이디를 받아올 수 있음)
- index: 스레드 번호
- value: 스레드에서 함수 동작이 끝나면 스레드가 종료되면서 리턴값이 저장됨
while문을 돌면서 스레드 10개가 생성된다
스레드 구조체의 인덱스 (threads[index].index)에는 현재 스레드 번호 (index) 가 들어감
pthread_create는 에러 상황에 음수값을 리턴하므로, 이것을 임시 변수 (thread_arr) 에 저장해두었다가 에러가 나면 (thread_err < 0) 프로그램을 종료시킴 (exit(0))
스레드 생성이 완료되면 printf를 통해 스레드가 생성되었음을 알리고, index 증가
이때 스레드가 while문 속에서 생성되는 시점과, 만들어진 스레드가 동작하는 시점이 서로 다르다
이는 스레드가 함수를 호출할 때 발생하는 시간 지연 때문인 것으로 추정됨
또한, main 함수 (Thread index %d created를 출력하는 함수) 와 각 스레드들이 번갈아가면서 동작하는 것 또한 볼 수 있음
2. 스레드 동작
void *thread_function(void *param)
{
int index;
t_thread_struct *data;
index = 0;
data = (t_thread_struct *)param;
while (index < 10)
{
printf("%dth Thread %llu\t:\tindex %d\n",
data->index, (unsigned long long)data->thread_id, index++);
sleep(1);
}
return (0);
}
스레드는 자신의 스레드 ID (data→thread_id) 와 스레드 번호 (data→index), while문 호출 횟수 (index)를 출력하고 1초 쉬는 것 (sleep(1))을 반복한다
각 스레드는 index 0 ~ 9까지를 순서대로 출력하므로, 출력 결과물을 보면 각 스레드가 병렬로 (동시에) 수행되는 것처럼 보이는 것을 확인할 수 있다
while문 호출 횟수는 나름 묶여서 같이 나오지만 (휴식 시간이 1초로 다소 길어서 그런듯) 스레드 순서는 완전히 뒤죽박죽인 것을 확인할 수 있다
스레드 생성 시점과 동작 순서가 일치하진 않는 듯 함
3. 스레드 종료 대기
index = -1;
while (++index < 10)
{
pthread_join(threads[index].thread_id,
(void **)&(threads[index].value));
printf("Thread index %d joined\n", index);
//pthread_detach(threads[index].thread_id);
//printf("Thread index %d detached\n", index);
}
printf("All the threads call finished\n");
exit(0);
모든 스레드 동작이 끝나면 (0 ~ 9번 스레드가 index 9까지 전부 출력했을 경우) main문의 남은 줄이 실행된다
pthread_join 함수 또는 pthread_detach 함수를 통해 스레드를 종료하고 자원을 반납한 후, Thread index <스레드번호> joined / detached 라는 문장이 출력된다 (join 또는 detach 호출되는 시점을 알기 위함)
4. 모든 동작이 끝나면
printf("All the threads call finished\n");
exit(0);
모든 스레드 콜이 끝났다는 문장을 출력하고 종료
pthread_join vs pthread_detach
둘 다 스레드의 자원을 반납해주는 함수라는 점에서 같지만, 동작 시점과 행동이 약간 다르다
pthread_join
int pthread_join(pthread_t phread, void **value_ptr)
void **value_ptr에 빈 변수 포인터를 넣음으로써 스레드가 종료될 때 반환되는 리턴값을 받아올 수 있다 (자동으로 해당 변수에 리턴값 포인터가 들어간다)
pthread_join은 joinable한 스레드를 스레드가 종료될 때까지 대기시키는 역할을 겸한다
pthread_join은 인자로 넣은 스레드 id (pthread_t thread) 에 해당하는 스레드 (joinable함) 가 종료될 때까지 (연결된 함수 호출이 끝날 때까지) 대기한다
그렇기 때문에 pthread_join이 호출된 부모 스레드 (대표적으로 main 함수 등) 가 같이 suspend되며, pthread_join의 인자로 받은 모든 자식 스레드 (부모 스레드 내에서 pthread_create를 통해 생성된 스레드) 가 끝나고 자원이 회수될 때까지 부모 스레드 또한 대기한다
모든 자식 스레드의 자원이 회수되고 나면, 부모 스레드 에서 pthread_join 이후의 라인이 실행된다
따라서 만약 자식 스레드가 무한루프일 경우, 부모 스레드의 나머지 라인은 실행될 수 없다
위의 프로그램 실행 결과를 확인해보면, pthread_join을 통해 스레드 0 ~ 9번의 자원을 회수시킬 때 모든 스레드가 printf를 10번 호출한 뒤 pthread_join 이후의 라인 (Thread index # joined 출력) 이 실행되는 것을 확인할 수 있다
이 부분을 통해 main 스레드 (스레드 0 ~ 9번을 Create했던 스레드) 가 pthread_join에 의해 suspend되고, 스레드 0 ~ 9번이 모두 종료된 후 main이 동작을 재개하는 것을 볼 수 있다
동작 순서
1. 첫 번째 while문 내부에서 스레드 0 ~ 9번 생성 (pthread_create)
모든 스레드는 생성과 동시에 번갈아가면서 병렬로 함수를 실행한다 (while 내부의 printf 10번 호출)
이때 호출되는 순서는 매번 섞이는듯
2. 두 번째 while문 내부에서 스레드 0에 대한 pthread_join 호출
이때 pthread_join에 의해 main 스레드는 스레드 0이 끝날 때까지 대기
3. main 스레드는 계속 대기 상태이고, 스레드 0 ~ 9번은 함수 내의 내용 수행
4. 스레드 0이 끝나고 pthread_join에 의해 자원이 반납되면, Thread index 0 joined가 출력된 후, 다음 while 루프에서 스레드 1에 대한 pthread_join 호출
5. 스레드 1에 대한 pthread_join 때문에 또 다시 main 스레드는 스레드 1이 끝날 때까지 대기
6. 스레드 1이 끝나고 pthread_join에 의해 자원이 반납되면, Thread index 1 joined가 출력된 후, 다음 while 루프에서 스레드 2에 대한 pthread_join... 이하 4 ~ 6번 반복
7. 스레드 9가 끝나고 pthread_join에 의해 자원이 반납되면, Thread index 9 joined가 출력된 후, 모든 스레드가 종료되었고 더이상 pthread_join이 호출되지 않기 때문에 while루프 탈출 + 마지막 문장 (All the threads call finished) 출력, exit(0)
pthread_detach
int pthread_detach(pthread_t)
pthread_join과 다르게, 함수의 리턴값을 받아올 방법이 없다
인자로 넣은 스레드 id (pthread_t thread) 에 해당하는 스레드의 속성을 joinable에서 detached로 바꾸며, 이렇게 되면 부모 스레드 (자식 스레드를 pthread_create했던 스레드) 와의 의존성이 사라진다
그렇기 때문에 pthread_join 때는 자식 스레드가 모두 종료될 때까지 부모 스레드가 대기했던 반면, pthread_detach를 이용하여 detached된 스레드와 부모 스레드가 함께 동작하며, (자식 스레드의 함수 라인과 부모 스레드의 함수 라인이 번갈아가며 호출됨) 부모 스레드가 종료되어도 자식 스레드는 살아남는다
위 프로그램 실행 결과를 보면, 0 ~ 9번 스레드가 모두 생성되고 그 다음 while문으로 진입하자마자 모든 스레드가 detached 되었다고 나온다
이는 아직 스레드 0 ~ 9번이 종료된 것은 아니며, main 스레드와의 의존성이 사라졌기 때문에 main 스레드가 다른 스레드들과 같이 실행되는 과정에서 printf가 출력된 것이다
그리고 *all the threads call finished* 라는 문장이 출력된 후 main 스레드가 exit(0) 에 의해 끝나버리면, 자식 스레드의 자원이 모두 회수되고 프로세스 자체가 종료된다
만약 main 스레드가 무한루프 스레드이거나, 다른 스레드 0 ~ 9번보다 종료 시점이 느리다면 위에서처럼 스레드 0 ~ 9번이 강제종료되지 않고 main 스레드가 살아있는 동안 다른 스레드들도 다 같이 살아있다
main 스레드가 무한루프가 아니면서 다른 스레드 0 ~ 9번보다 종료 시점이 빠르다면 위의 프로그램처럼 main 스레드가 끝나면서 (메인 스레드는 프로세스와 다름없으므로) 자식 스레드도 모두 끝나버리는 것
상위 스레드가 main 스레드가 아니라면, detached된 스레드를 호출한 상위 스레드가 종료되어도 detached된 스레드는 살아남는다
동작 순서
1. 첫 번째 while문 내부에서 스레드 0 ~ 9번 생성 (pthread_create)
모든 스레드는 생성과 동시에 번갈아가면서 병렬로 함수를 실행한다 (while 내부의 printf 10번 호출)
2. 두 번째 while문 내부에서 스레드 0에 대한 pthread_detach 호출
각 스레드의 속성이 joinable에서 detached로 변경됨과 동시에, *index # detached* 문장이 출력된다
3. 모든 스레드가 detached되었고 while 루프를 탈출하면, 마지막 문장 (*All the threads call finished*) 이 출력되고, exit(0)에 의해 메인 스레드가 종료되면서 detached된 스레드 0 ~ 9번이 같이 종료된다
따라서 다른 스레드들의 출력 결과가 표시되지 않는 것을 볼 수 있다
참고
https://bitsoul.tistory.com/157
https://42kchoi.tistory.com/301?category=966538
슬랙에서 질문 답변해주신분들 감사합니다
'42 > 42s Cursus' 카테고리의 다른 글
[Rank 3] Philosophers - 사용가능 함수 정리 [Mandatory] (0) | 2021.09.04 |
---|---|
[Rank 3] Philosophers - Joined & Detached 스레드 예제 (0) | 2021.09.04 |
[Rank 3] Philosophers - 프로세스와 스레드 (0) | 2021.09.04 |
[Rank 3] Philosophers (0) | 2021.09.04 |
42서울 la piscine #외전 (8) | 2021.09.01 |