티스토리 뷰
c에서 여러 작업을 병렬로 처리 할때는 스레드나 프로세스 복제를 사용한다.
이때 스레드는 특정 작업을 병렬로 처리할때 많이 사용되며, 이번 프로젝트에서 클라이언트 전송에 사용하였다.
스레드란?
스레드는 프로세스 내에서 실행되는 흐름의 단위를 말한다.
일반적으로 한 프로그램은 하나의 스레드를 가지고 있지만, 프로그램 환경에 따라 둘 이상의 스레드를 동시에 실행할 수 있다.
이러한 실행 방식을 멀티스레드라고 한다.

보통 스레드는 프로세스 복제와 많이 비교가 되는데, 이 둘의 가장 큰 차이점은 자원 공유이다.
스레드는 같은 스레드끼리 자원을 공유하며, 서로에게 영향을 줄 수 있다.
그에 반해, 프로세스 복제는 완전히 별개의 영역을 가진 독립적인 프로세스를 만든다.
때문에 한 프로세스가 다른 프로세스의 영향을 줄 수는 없다.
이와같은 스레드의 특성 때문에, 하나의 스레드가 공유된 자원에 접근하여 동기화 문제를 발생 시킬수 있다.
때문에 뮤텍스와 같이 한 스레드 자원에 접근 중일때 다른 스레드를 잠시 정지시키던가, 스레드마다 사용할 자원을 미리 정하여 다른 쪽에 영향을 미치지 않게하는 방법이 있다.
이번 프로젝트에서는 후자의 방법으로 스레드의 안정성을 유지할 것이다.
pthread
pthread는 POSIX 표준에 따라 정의된 스레드 라이브러리로 c에서 보통 이를 사용하여 스레드를 생성한다.
프로젝트에 사용된 코드를 보면서 어떻게 스레드가 실행되는지 설명하겠다.
void thread_create(unsigned char **update_data, transfer_header_t *update_header, int *client_socket)
{
if (NULL == update_data)
{
printf("thread_create 매개변수 오류\n");
return;
}
// 스레드 생성
pthread_t threads[MAX_CLIENTS];
memset(threads, 0, sizeof(threads));
thread_data_t thread_data[MAX_CLIENTS];
memset(thread_data, 0, sizeof(thread_data));
for (int index = 0; index < MAX_CLIENTS; index++)
{
if (0 == client_socket[index])
{
continue;
}
else
{
// 각 스레드 구성
thread_data[index].serialized_data = *update_data;
thread_data[index].socket = client_socket[index];
thread_data[index].serialized_data_size = update_header->total_size;
// 스레드 생성 및 실행할 함수와 인수 전달
pthread_create(&threads[index], NULL, file_send_thread, &thread_data[index]);
}
}
// 생성된 스레드가 종료될 때까지 대기
for (int thread_index = 0; thread_index < MAX_CLIENTS; thread_index++)
{
pthread_join(threads[thread_index], NULL);
}
//전송 종료 후, update_data 할당 해제
if (NULL != *update_data)
{
free(*update_data);
*update_data = NULL;
}
}
해당 코드는 변경된 파일의 직렬화 데이터를 넘겨 받고, 해당 데이터를 연결된 클라이언트에 전송하는 코드이다.
이때 스레드를 사용하여 병렬로 전송하는 구조를 가졌다.
pthread_t threads[MAX_CLIENTS];
memset(threads, 0, sizeof(threads));
thread_data_t thread_data[MAX_CLIENTS];
memset(thread_data, 0, sizeof(thread_data));
우선 스레드 생성전에 pthread_t,thread_data_t 구조체 배열을 선언한다.
여기서 pthread_t는 pthread가 제공하는 구조체로 스레드 식별자를 할당하기 위해 사용한다.
thread_data_t 는 사용자 정의 구조체로 스레드에서 사용될 변수들을 저장한 구조체이다.
스레드에서는 단일 인자만 허용하기에, 함수같이 여러 인자를 전달할 수 없다.
때문에 여러 인자가 필요한 경우 구조체에 담아 한번에 전달한다.
for (int index = 0; index < MAX_CLIENTS; index++)
{
if (0 == client_socket[index])
{
continue;
}
else
{
// 각 스레드 구성
thread_data[index].serialized_data = *update_data;
thread_data[index].socket = client_socket[index];
thread_data[index].serialized_data_size = update_header->total_size;
// 스레드 생성 및 실행할 함수와 인수 전달
pthread_create(&threads[index], NULL, file_send_thread, &thread_data[index]);
}
}
소켓이 저장된 배열을 읽으면서, 연결된 소켓이 있을 경우, pthread_create로 스레드를 생성하고, 빈 배열일 경우는 넘어가는 코드이가.
이때, 각 스레드에서 사용될 데이터를 구조체에 할당한다.
이렇게 하면 자원 공유측면에서, 각 스레드가 각자의 구조체를 인자로 받고 이를 사용하기에 동기화 문제가 발생하지 않는다.
구조체는 전송할 데이터, 클라이언트의 소켓,그리고 전송 데이터의 크기가 저장된다.
pthread_create는 첫인자에 스레드 식별자를 넣고, 3번째 인자에 스레드를 생성하고 실행할 스레드 함수를 넣는다. 인자가 필요할 경우 4번째 인자에 넣는다.
// 생성된 스레드가 종료될 때까지 대기
for (int thread_index = 0; thread_index < MAX_CLIENTS; thread_index++)
{
pthread_join(threads[thread_index], NULL);
}
위에서 스레드가 생성되면, 메인 스레드(프로그램 시작시 생성되 스레드)는 다음 코드를 진행한다.
이때 메인 스레드도 스레드니 자원 동기화 문제가 발생할 수 있다.
때문에 pthread_ join으로 모든 스레드가 끝날 때까지 대기한다.
스레드 함수는 다음과 같다.
void *file_send_thread(void *arg)
{
thread_data_t *data = (thread_data_t *)arg;
int socket = 0;
socket = data->socket;
unsigned char *serialized_data = NULL;
serialized_data = data->serialized_data;
unsigned long serialized_data_size = 0;
serialized_data_size = data->serialized_data_size;
send(socket, serialized_data, sizeof(transfer_header_t) + serialized_data_size, 0);
pthread_exit(NULL);
}
인자가 있다면 arg를 사용해서 받는다.
그리고 내부 코드, 여기서는 클라이언트에 데이터를 전송하는 로직을 실행한다.
만약 이 상태로 함수를 종료한다면, 이 프로세스애서 스레드가 종료되지 않고 계속 남아있게 된다.
이를 막기위해서는 스레드 함수 마지막에 pthread_exit()를 사용하여 명시적으로 스레드를 종료시켜야 한다.
'프로젝트 > 파일 동기화' 카테고리의 다른 글
프로젝트: 파일 동기화 - 압축 (0) | 2024.02.20 |
---|---|
프로젝트: 파일 동기화 - 데이터 직렬화 (1) | 2024.02.20 |
프로젝트: 파일 동기화 - 동기화 리스트와 파일 변경 감지 (0) | 2024.02.15 |
프로젝트: 파일 동기화 - TCP 소켓 통신 (1) | 2024.02.14 |
프로젝트: 파일 동기화 - 해쉬 테이블 (1) | 2024.02.14 |