치춘짱베리굿나이스

[Rank 3] Philosophers - 스레드 함수 예 본문

42/42s Cursus

[Rank 3] Philosophers - 스레드 함수 예

치춘 2021. 9. 4. 11:31

아니 마크다운이 다 깨지네... 하느님 나에게 왜 이딴 시련을...

코드

/* ************************************************************************** */
/*                                                                            */
/*                                                        :::      ::::::::   */
/*   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

슬랙에서 질문 답변해주신분들 감사합니다

Comments