2023.11.19 - [Network/소켓통신(Linux)] - 멀티스레드 개념
멀티스레드 개념
1. 스레드 기초 1) 소켓 응용 프로그램과 멀티스레드 멀티스레드를 사용하지 않는 경우 문제 두 개 이상의 클라이언트가 서버에 접속할 수 있으나, 서버가 동시에 두 개 이상의 클라이언트를 서
kmg0157.tistory.com
이전 게시글을 통해 개념 참고
1. 스레드 API
1) 스레드 생성과 종료
- 리눅스에서 스레드를 생성할 때는 pthread_create() 함수 사용
#include<pthread.h>
int pthread_create(
pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine) (void *),
void *arg
)
// 성공 시 0, 실패 시 오류 코드 리턴
- thread
- 스레드 생성이 성공하면 스레드 식별자가 저장
- attr
- 생성할 스레드의 속성을 제어
- start_routine
- 스레드 함수의 시작 주소
- arg
- 스레드 함수에 전달할 인수
- 스레드를 종료하는 방법
- 스레드 함수가 리턴
- 스레드 함수 안에서 phread_exit() 함수를 호출
- 다른 스레드가 pthread_cancel() 함수를 호출하여 스레드에 취소 요청을 보냄
- 메인 스레드가 종료하면 프로세스 내의 다른 모든 스레드가 강제 종료됨
- 일반적으로 1,2 번 방식을 사용하는 것이 바람직함
#include<pthread.h>
int pthread_eixt(
void *retval //종료 코드
)
#include<pthread.h>
int pthread_cancel(
pthread_t thread //취소할 스레드 식별자
)
EX) 스레드 생성과 종료, 인수 전달 연습
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
struct Point3D
{
int x, y, z;
};
void *MyThread(void *arg)
{
sleep(1);
Point3D *pt = (Point3D *)arg;
printf("Running MyThread() %lu: %d, %d, %d\n",
pthread_self(), pt->x, pt->y, pt->z);
return NULL;
}
int main(int argc, char *argv[])
{
int retval;
// 첫 번째 스레드 생성
pthread_t tid1;
Point3D pt1 = { 10, 20, 30 };
retval = pthread_create(&tid1, NULL, MyThread, &pt1);
if (retval != 0) return 1;
// 두 번째 스레드 생성
pthread_t tid2;
Point3D pt2 = { 40, 50, 60 };
retval = pthread_create(&tid2, NULL, MyThread, &pt2);
if (retval != 0) return 1;
printf("Running main() %lu\n", pthread_self());
sleep(2);
return 0;
}

- 메인 스레드와 더불어 MyThread 두 개의 ID가 출력되고, 각 스레드에 전달한 Point3D 구조체 내용도 확인 가능
2) 스레드의 최대 개수

- 32비트 리눅스에서 실행되는 모든 프로세스는 4GB의 가상 주소 공간(Virtual Address Space)을 각각 할당
- OS 커널 영역을 제외하고 하위 3GB만 자유롭게 접근하여 사용할 수 있는 공간
- 스레드를 하나 만들 때마다 하위 3GB에서 스택을 위한 공간을 할당 받음
- pthread_create() 함수를 호출할 때 기본 스택 크기를 8MB라 가정하면, 3GB/8MB = 384가 단순 계산된 최대개수
- 그러나 하위 3GB에는 실행 파일, 공유 라이브러리, 힙, 환경 변수 등을 위해 사용되는 영역이 존재
- 따라서 생성가능한 스레드의 최대 개수는 더 줄어듬
- 스레드를 필요 이상으로 많이 생성하면 메모리를 많이 소모할 뿐 아니라 스레드 간에 컨텍스트 전환 횟수가 증가하여 오히려 성능 저하
3) 스레드 제어
- 스레드 우선순위 변경
- 스레드는 리눅스 운영체제 내부(Kernel)로 들어가면 태스크(Task)라고 불리는 실행 단위와 일대일 대응
- 프로세스 내부의 스레드 하나하나가 커널 내부의 태스크로 표현되며, 실질적인 실행 주체가 됨
- CPU 스케줄링(Scheduling)
- 각 태스크에 CPU 시간을 적절히 분배하기 위한 정책
- 리눅스 운영체제에서는 항상 여러 태스크가 CPU 시간을 사용하기 위해 경쟁하기 때문에
- 태스크 스케줄링, 프로세스 스케줄링 등으로도 불림
- 각 태스크에 CPU 시간을 적절히 분배하기 위한 정책
- 우선 순위 결정 요소
- 스케줄링 정책(Scheduling Policy)
- 실시간(Real-Time)과 정규(Normal) 두 가지 정책
- 실시간 정책을 따르는 태스크는 정규 정책을 따르는 태스크보다 항상 우선순위 높음
- 스케줄링 우선순위(Schduling Priority)
- 실시간 스케줄링 정책은 1(최저)~99(최고)범위의 우선순위 지원
- 정규 스케줄링 정책은 고정된 순위를 지원하지 않으므로 우선순위를 0으로 간주
- 스케줄링 정책(Scheduling Policy)
EX) 스레드 우선순위 변경 연습
- nice() 를 호출하면 호출한 스레드의 우선 순위가 변경됨
- 인수인 inc의 값은 -20~+19까지 가능
- 리턴값인 Nice 값이 리눅스 시스템 콜의 실패를 뜻하는 -1을 가질 수 있음
- nice() 호출 전에 전역 errno를 0으로 초기화하고, 호출 후에 리턴값과 errno값을 확인해야
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
void *MyThread(void *arg)
{
// 우선순위를 높게 설정한다.
errno = 0;
int retval = nice(-20);
if (retval < 0 && errno != 0) {
perror("nice() in MyThread");
exit(1);
}
while (1) {
write(1, ".", 1);
}
return 0;
}
int main()
{
// CPU 개수를 알아낸다.
int numberOfProcessors = sysconf(_SC_NPROCESSORS_ONLN);
// CPU 개수만큼 스레드를 생성한다.
for (int i = 0; i < numberOfProcessors; i++) {
pthread_t tid;
pthread_create(&tid, NULL, MyThread, NULL);
}
// 우선순위를 낮게 설정한다.
errno = 0;
int retval = nice(19);
if (retval < 0 && errno != 0) {
perror("nice() in main");
exit(1);
}
sleep(1);
while (1) {
write(1, "*", 1);
}
return 0;
}

- MyThread의 우선순위가 높으므로 대부분 '.' 이 출력되지만, 메인 스레드가 출력하는 '*'도 확인 가능
- 스레드의 우선 순위가 고정되어 있지 않기 때문에 특정 스레드에 높은 우선순위를 부여하더라도
낮은 우선 순위를 가진 스레드도 실행 기회가 주어짐
4) 스레드 종료 기다리기
- 스레드가 생성되면 다른 스레드와 경쟁하며 독립적으로 실행됨
- 때로는 한 스레드가 다른 스레드의 종료 여부를 확인해야 할 때가 있음
- pthread_join() 함수를 사용하여 특정 스레드가 종료할 때까지 대기
#include<pthread.h>
int pthread_join(
pthread_t thread //종료를 기다릴 대상 스레드 식별자
void** retval //스레드 함수의 리턴값을 받는데 사용
)
EX) 스레드 종료 기다리기 연습
#include <stdio.h>
#include <pthread.h>
int sum = 0;
void *MyThread(void *arg)
{
int num = (int)(long long)arg;
for (int i = 1; i <= num; i++)
sum += i;
return 0;
}
int main(int argc, char *argv[])
{
int num = 100;
pthread_t tid;
pthread_create(&tid, NULL, MyThread, (void *)(long long)num);
printf("스레드 실행 전. 계산 결과 = %d\n", sum);
pthread_join(tid, NULL);
printf("스레드 실행 후. 계산 결과 = %d\n", sum);
return 0;
}

- 스레드 실행 전에는 결과가 틀리지만, 실행 후에는 올바른 결과 출력
'Network > 소켓통신(Linux)' 카테고리의 다른 글
멀티스레드 : 리눅스 2 (1) | 2023.11.19 |
---|---|
멀티스레드 개념 (2) | 2023.11.19 |
TCP 동작 과정 및 함수 정리 (0) | 2023.11.19 |
데이터 전송하기 (0) | 2023.09.17 |
TCP 서버 - 클라이언트 구조 (2) | 2023.09.02 |