컨텍스트 스위치
스레드에서 컨텍스트 스위치란? 특정 스레드를 실행하다가 다른 스레드를 실행하는 과정을 의미한다.
그렇다면 왜 중간에 다른 스레드를 실행하는 걸까?
프로그램을 실행하다 보면 동시에 여러 프로그램이 실행되는 걸 볼 수 있다.
물론 여러 코어에서 동시에 프로그램을 실행해 작업을 할 수도 있지만, 하나의 코어에서 순간적으로 A작업, B작업을 번갈아가며 실행해 동시에 실행되는 것처럼 보이게 할 수도 있다.
즉, 여러 작업이 동시에 실행되도록 번갈아가며 실행하기 위해 컨텍스트 스위칭을 한다고 생각하면 된다.
여기서 멀티 스레드를 사용할 때, 주의해야 할 점이 있다.
예를 들어 A=10 이고, 스레드 1은 A+=1이라는 연산을 하려고 한다.
스레드 2는 A*=2라는 연산을 하려고 한다.
스레드 1을 실행하던 도중 중간에 컨텍스트 스위칭이 일어나게 되면, 작업을 멈추게 되고 스레드 2가 실행 된다.
이때, A의 값은 변경되지 않았고 스레드 2의 작업이 완료되면 A=20이 된다.
하지만 다음에 스레드 1이 마저 실행된다면, A=11이라는 값이 저장 될 수도 있다.
(물론 어느 작업 과정에서 컨텍스트 스위칭이 일어나느냐에 따라 다른 값이 나올 수도 있다.)
이처럼 같은 자원을 공유하는 경우 중간에 컨텍스트 스위칭이 일어나면 결과 값을 정확하게 예상할 수 없는 문제가 생길 수 도 있다.
뮤텍스 (mutex)
이러한 문제를 해결하기 위해 자원에 대한 접근을 막는 방법 중 mutex를 사용해 보겠다.
mutex는 mutex.lock()로 잠그고 mutex.unlock()로 자원에 대한 잠금을 관리하기도 하지만,
lock_guard<mutex>를 사용하면 자동으로 잠금을 해제해주는 Class를 제공한다.
int a;
int n;
recursive_mutex a_mutex;
{
lock_guard<recursive_mutex> a_lock(a_mutex);
n = a;
a++;
}
위와 같이 사용할 경우 lock_guard가 포함된 {}의 자원들에 대한 잠금이 걸리게 된다.
그리고 {}가 끝나면 자원들에 대한 잠금이 해제된다.
이를 활용해 소수를 멀티스레드로 구하는 코드를 작성해 보자
#include <iostream>
#include <vector>
#include <chrono>
#include <thread>
#include <memory>
#include <mutex>
using namespace std;
const int MaxCount = 100000;
const int ThreadCount = 4;
bool IsPrimeNumber(int num)
{
if (num == 1)
return false;
else if (num == 2)
return true;
for (int i = 2; i < num; i++)
{
if (num % i == 0)
{
return false;
}
}
return true;
}
void main()
{
int num = 1;
vector<int> primes;
recursive_mutex num_mutex;
recursive_mutex t_mutex;
recursive_mutex primes_mutex;
vector<thread> threads;
for (int i = 0; i < ThreadCount; i++) {
threads.push_back(thread([&] //람다 함수
{
while(num<=MaxCount)
{
int n;
{ //자원 잠금
lock_guard<recursive_mutex> num_lock(num_mutex);
n = num;
num++;
}
if (n >= MaxCount)
{
break;
}
if (IsPrimeNumber(n))
{ //자원 잠금
lock_guard<recursive_mutex> primes_lock(primes_mutex);
primes.push_back(n);
}
}
}));
}
for (int i=0; i<threads.size(); i++)
{
threads[i].join();
}
}
위 코드를 보면 자원에 대한 접근을 lock_guard로 잠궈 공유된 자원 num에 대한 접근을 막고 num이 1씩 증가할 수 있도록 했다.
+ 여기서 사용된 recursive_mutex와 mutex의 차이점은 recursive_mutex는 한 스레드가 뮤텍스를 여러 번 반복해서 잠그는 것을 처리해 준다.
예를 들어 lock(R_mutex)로 두번 잠궜다고 가정을 하면 unlock(R_mutex)로 한 번 잠금을 해제해도 잠금은 해제되지 않는다. 한번 더 unlcok(R_mutex)를 사용해 잠금을 해제해줘야 잠금이 해제된다.
그리고 thread에서 사용한 람다함수를 보면 []대신 [&]를 사용한 것을 볼 수 있는데, 이는 외부 변수를 참조값으로 사용 하겠다는 의미다.
Thread의 Count를 늘이거나 줄이면 실제로 코드의 완료 시간이 줄어들거나 늘어나는 모습을 확인할 수 있다.
'C++' 카테고리의 다른 글
[프로그래밍/C++] 헤더파일과 소스파일을 분리하는 이유 (0) | 2024.02.08 |
---|---|
[프로그래밍/C++] 스레드(Thread) 사용하기 (0) | 2024.02.03 |