쓰레드(Thread)

MwG·2024년 11월 16일

C++

목록 보기
2/14

프로세스(Process)

프로세스는 운영체제에서 실행되는 프로그램의 최소단위

프로세스는 cpu의 코어에서 실행이 된다.

쓰레드(Thread)

CPU 코어에서 돌아가는 프로그램 단위이다.
CPU 한개의 코어에서 한 번에 한 개의 쓰레드의 명령을 실행시킨다.

한개의 프로세스는 최소 한 개의 쓰레드로 이루어짐
두 개 이상의 쓰레드는 멀티쓰레드라고 부른다.

쓰레드와 프로세스의 차이점

프로세스는 메모리를 서로 공유하지 않는다. 즉 프로세스1 과 프로세스2는 서로의 메모리에 접근할 수 없다.

반면에, 같은 프로세스 내에 있는 쓰레드는 서로 메모리를 공유한다. 만약 프로세스1에 쓰레드1, 쓰레드2, 쓰레드3이 있다면 이 세개는 서로 메모리를 공유하는 것이다.

멀티 쓰레드를 쓰는 이유?

병렬가능한 작업을 처리할 때 매우 효율적이기 때문이다.

EX) 만약 for문으로 i = 1 부터 1000까지 계산해야 하는 작업이 있고 덧셈당 1초가 걸린다고 하자. 만약 싱글 쓰레드로 작업을 수행하면 1000초가 걸리게 될 것이다.

하지만 이것을 10개의 쓰레드로 나누어 i = 1 부터 100/ 101부터 200 / 201부터 300/.....이런 식으로 할 경우 총 110초가 걸릴 것이다.(10초는 마지막에 각각의 쓰레드에서 나온 값을 합치는데 걸리는시간) 대략 10배 더 빠르게 작업을 수행 할 수 있는 것이다.

이때 병렬 가능한 작업을 처리할 때가 중요한 조건이다.
만약 전에 계산한 값이 다음 값에 영향을 주는(dependent,데이터 의존)일 경우 병렬적이기 어렵게 된다.

쓰레드 생성 코드 예시

#include <iostream>
#include <cstring>
#include <string>
#include <vector>
#include <stack>
#include <queue>
#include <algorithm>
#include <math.h>
#include <set>
#include <map>
#include <deque>

#include <thread>

using namespace std;

using std::thread;

void func1()
{
	for (int i = 0; i < 4; i++)
	{
		cout << "쓰레드1 작동중!\n";
	}
}

void func2()
{
	for (int i = 0; i < 4; i++)
	{
		cout << "쓰레드2 작동중!\n";
	}
}

void func3()
{
	for (int i = 0; i < 4; i++)
	{
		cout << "쓰레드3 작동중!\n";
	}
}


int main()
{
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);

	thread t1(func1);
	thread t2(func2);
	thread t3(func3);
	
	t1.join();
	t2.join();
	t3.join();

	return 0;
}

#include <thread>

thread헤더 파일 추가

이후 thread객체를 생성해주고 그 안에 func함수를 인자로 전달해준다.

즉 예시 코드에서는 서로 다른 세 개의 쓰레드에서 각각 함수를 실행하게 된다.

thread.join()함수는 해당하는 쓰레드들이 종료하면 리턴하는 함수이다.
만약 이것을 설정하지 않는다면 main함수가 먼저 종료를 해버리게 되어 thread객체의 소멸자가 호출되게 된다.
->예외가 발생하게 됨..

비슷한 함수로 .detach()도 존재하는데 main과 독립적으로 만든다(잊어버린다)고 생각하면 된다. 백그라운드에서 계속 돌아감.

#결과

결과를 보면 쓰레드 작동 순서가 뒤죽박죽인 것을 알 수 있다.
이것은 운영체제에서 어떤 코어에 할당하고 어떤 순서로 스케줄할지는 상황에 따라 달라지기 때문이다.

쓰레드에 인자 전달하기

1부터 10000까지의 합을 멀티쓰레드를 통해 구해보기


#include <iostream>
#include <cstring>
#include <string>
#include <vector>
#include <stack>
#include <queue>
#include <algorithm>
#include <math.h>
#include <set>
#include <map>
#include <deque>

#include <thread>

using namespace std;

using std::thread;

void worker(vector<int>::iterator start, vector<int>::iterator end,
						int *result)
{
	int sum = 0;

	for (auto iter = start; iter != end; iter++)
	{
		sum += (*iter);
	}

	*result = sum;

	thread::id this_id = this_thread::get_id(); //현재 쓰레드의 주소? id 가져오기
	printf("쓰레드 %x에서 %d부터 %d까지 계산한 결과: %d \n", this_id, *start, *(end - 1), sum);
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);

	vector<int> nums(10000);

	for (int i = 0; i < 10000; i++)
	{
		nums[i] = i;
	}

	vector <int>partial_sums(4); //각 쓰레드에서 구해온 합들..

	vector<thread> workers;

	for (int i = 0; i < 4; i++)
	{
		workers.push_back(thread(worker, nums.begin() + i * 2500
			, nums.begin() + (i + 1) * 2500, &partial_sums[i]));

	}


	for (int i = 0; i < 4; i++)
	{
		workers[i].join();
	}

	int total = 0;

	for (int  i = 0; i < 4; i++)
	{
		total += partial_sums[i];
	}

	cout << "전체 합은 " << total;

	return 0;
}

쓰레드에는 반환값이 없기에 값을 저장하고 싶다면 포인터으 형태로 전달하기.
쓰레드 인자 전달할 때 함수를 전달할 경우 bind처럼 함수 다음에는 그 함수의 인자를 순서대로 전달하기

printf를 쓴 이유 cout을 쓸 경우 cout << 쓰레드 << id ...이런 형태에서 쓰레드까지만 실행을 했는데 다음 쓰레드로 넘어갈 수 있기에 방해 받지 않고 전체메시지를 출력하기 위해서

#결과

<출처>
모두의 코드

0개의 댓글