공log/[CS]
[Study] #1 Multi-Thread
ming_OoO
2023. 7. 26. 22:51
728x90
🌘 Thread(스레드)
Light Weight Process 라고도 함
- 프로세스
- 프로세스 간에는 각 프로세스의 데이터 접근이 불가능
- 스레드동시 실행 가능
- 프로세스의 데이터를 모두 접근할 수 있음
- 하나의 프로세스에 여러개의 스레드 생성 가능

🌗 Multi Thread
스레드는 일동의 함수로 구현되어 각각의 스레드마다 별도의 스택 영역을 가지고 동작한다.
- 멀티 태스킹과 멀티 프로세싱
- 멀티 태스킹 : 다수의 작업이 중앙 처리 장치(CPU)와 같은 공용자원을 나누어 사용하는 것 (→ 스레드)
- 멀티 프로세싱 : 하나의 프로세스에서 실행되어야 할 작업들을 여러개로 분리한후에 각각의 CPU 코어에 넣어서 병행하여 실행하게끔 만듬. 이때 스레드를 만들어 분리하면 되고 스레드들을 지원해서 여러 코어에서 스레드가 동시에 실행하게끔 스케쥴링하는 방식
- 장점
- 사용자에 대한 응답성이 향상 (= 성능 향상)
- 자원 공유 효율
- 단점
- 스레드 중 한 스레드만 문제가 발생해도, 전체 프로세스가 영향을 받음
- 스레드를 많이 생성하면 Context Switching이 많이 발생 → 성능 저하
- 동기화 이슈로 비정상적으로 동작할 수 있다. → 동기화 코드 추가
🌖 Single and Multithreaded Processes

🌔 Pthread
- POSIX Thread의 약자
- 유닉스계열 POSIX시스템에서 병렬적으로 작동하는 소프트웨어를 작성하기 위하여 제공하는 API
- 스레드를 편하게 만들수 있게 도와주는 API
- POSIX (Portable Operating System Interface)란 다른 운영체제들 사이의 호환성을 위해 IEEE에서 만든 표준이다. 즉, 한 운영체제에서 개발한 프로그램을 다른 운영체제에서도 쉽게 돌아가도록 하는 것 → 실행 파일이 실행된다는 것이 아니라 소스 코드에서 호환이 된다는 의미
- Pthread API
- 스레드 생성과 관리: pthread_create(), pthread_exit(), pthread_join() 등의 함수를 통해 스레드를 생성하고 종료하며, 생성된 스레드들의 실행을 조절
- 동기화 기능: pthread_mutex_t, pthread_rwlock_t, pthread_cond_t 등의 동기화 객체를 사용하여 스레드 간의 상호작용과 동기화를 관리
- 사용자 레벨 또는 커널 레벨로 제공 가능
- 사용자 레벨 스레드 (User-level Thread): 스레드 관리를 응용 프로그램 자체가 담당합니다. 운영체제는 스레드가 여러 개라도 하나의 프로세스로 인식하며, 프로세스에 할당된 자원만을 사용합니다. 사용자 레벨 스레드는 비교적 가볍고 빠른 성능을 제공하지만, 멀티코어 CPU를 모두 활용하지 못하는 단점이 있습니다.
- 커널 레벨 스레드 (Kernel-level Thread): 스레드 관리를 운영체제 커널이 담당합니다. 따라서 운영체제는 각 스레드를 별도의 자체적인 프로세스로 인식하며, 개별 스레드에 CPU와 메모리 자원을 할당합니다. 커널 레벨 스레드는 멀티코어 CPU를 활용하여 병렬적으로 실행될 수 있지만, 스레드 생성 및 관리에 추가적인 오버헤드가 발생할 수 있습니다.
- UNIX 운영 체제(Solaris, 리눅스, Mac OS X)에서 공통적으로 사용됨
- 이러한 운영 체제들은 POSIX 표준을 준수하고, 멀티스레드 프로그래밍에 적합한 환경을 제공하기 때문에 Pthread API가 보편적으로 사용
🌓 Pthreads Example
single thread와 multi thread에서 각각 2를 1억번씩 더하고 연산의 시간을 구하라.
- 리눅스에서 c파일을 생성하여 다음 코드를 작성한다.
- 시간차를 구하는 함수를 정의하고 main에서 single thread와 multi thread를 호출한다.
- single thread에서는 for문을 이용하여 연산을 1억번 반복한다.
- multi thread에서는 thread를 100개 생성하고 호출한다.
- thread함수에서는 연산을 100만번 반복하고 그 값들을 전역 변수로 정의한 배열 sum에 순차적으로 저장한다.
- 연산이 끝나면 배열 sum에 저장된 값들을 모두 합한다.
소스코드 in Linux
#include <pthread.h>
#include <stdio.h>
#include <sys/time.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <math.h>
double time_diff(struct timeval *start, struct timeval *end) //시간 차 구하는 함수
{
return (end->tv_sec - start->tv_sec) + 1e-6 * (end->tv_usec - start->tv_usec);
}
double do_single_thread() //single thread 함수
{
struct timeval start, end;
gettimeofday(&start, NULL); //시작시간
int num = 0;
for (int j = 0; j < 100000000; j++) { // 1억개
num += 2;
}
gettimeofday(&end, NULL); //종료시간
printf("single: %d\n", num);
return time_diff(&start, &end); //시간차 반환
}
int sum[100]; //thread의 연산 값을 저장하는 배열
void *func(void *arg) //thread 함수
{
int num = 0;
for (int i = 0; i < 1000000; i++) // 백만 개
num += 2;
int s = *(int *)arg; //변수 s에 정수형 count변수값 대입
sum[s] = num; // 배열 s에 각 thread에서 계산한 값 저장
pthread_exit(NULL);
}
double do_multi_thread()
{
struct timeval start, end;
gettimeofday(&start, NULL);
pthread_t tid[100]; //thread 100개 생성
int count[100] = {0}; //thread 순서를 저장하는 배열
for (int i = 0; i < 100; i++) { //thread갯수만큼 반복
count[i] = i;
if (pthread_create(&tid[i], NULL, func, &count[i]) != 0) //thead 동작 함수에 count[i]를 전달하여 호출
{
fprintf(stderr, "thread create error\n");
exit(1); //thread함수에 오류가 나면 빠져나감
}
}
for (int i = 0; i < 100; i++)
pthread_join(tid[i], NULL); //모든 thread가 종료될때까지 기다림
int total = 0; //배열 sum의 합을 구하는 변수
for (int i = 0; i < 100; i++) {
total += sum[i]; //모든 thread 값의 합
}
gettimeofday(&end, NULL);
printf("multi: %d\n", total);
return time_diff(&start, &end);
}
void print_diff(double single_thread_processing_time, double multi_thread_processing_time)
{
printf("시간 : %.6f, %.6f\n", single_thread_processing_time, multi_thread_processing_time); //시간 출력
}
int main(int argc, char *argv[])
{
double single_thread_processing_time = do_single_thread(); //single thread 호출
double multi_thread_processing_time = do_multi_thread(); // multi thread 호출
print_diff(single_thread_processing_time, multi_thread_processing_time); // 시간차 출력
return 0;
}
실행결과
single: 200000000
multi: 200000000
시간 : 0.980798, 0.166895
(시간은 실행할때마다 다 다르게 나올 수 있음)
→ 실행결과가 다르게 나올 수 있지만 결과적으로 multi thread 가 single thread 대비 약 6배 빠르게 수행 되었음.
🌒 Java Multithreaded Program
- 자바 threads는 JVM에 의해 관리됩니다.
- 일반적으로 기본 OS에서 제공하는 스레드 모델을 사용하여 구현
- Java 스레드는 아래의 코드를 통해 생성할 수 있습니다 (따로 구현할 필요 없음)
public interface Runnable
{
public abstract void run();
}
- 멀티스레드 구현 자바 코드
- 실행 시 Driver의 인자값으로 정수를 넣어주어야 멀티스레드로 실행된다.
- Drive 옆 실행 버튼 클릭 → 실행 구성 수정 → 프로그램 인수에 원하는 정수 값 입력 → 확인
class Sum {
private int sum;
public int getSum() {
return sum;
}
public void setSum(int sum) {
this.sum = sum;
}
}
class Summation implements Runnable {
private int upper;
private Sum sumValue;
public Summation(int upper, Sum sumValue) {
this.upper = upper;
this.sumValue = sumValue;
}
public void run() {
int sum = 0;
for (int i = 0; i <= upper; i++)
sum += i;
sumValue.setSum(sum);
}
}
public class Driver {
public static void main(String[] args) {
if (args.length > 0) {
if (Integer.parseInt(args[0]) < 0)
System.err.println(args[0] + " must be >= 0.");
else {
Sum sumObject = new Sum();
int upper = Integer.parseInt(args[0]);
Thread thrd = new Thread(new Summation(upper, sumObject));
thrd.start();
try {
thrd.join();
System.out.println
("The sum of " + upper + " is " + sumObject.getSum());
} catch (InterruptedException ie) {
}
}
} else
System.err.println("Usage: Summation <integer value>");
}
}
실행결과
입력한 인자 값이 10이라면 1부터 10까지의 합인 55가 sum 값이 된다.
→ The sum of 10 is 55 출력
728x90