멀티스레딩 환경에서 여러 스레드가 공유 자원(예: 동적 메모리, STL 컨테이너 등)에 동시 접근하면 데이터 경합(Data Race) 문제가 발생할 수 있습니다. 이를 방지하기 위해 락(lock)을 사용하여 특정 코드 영역에 대해 한 번에 하나의 스레드만 접근할 수 있도록 제어합니다.
// 모든 것을 아토믹으로 바꿀 수 있을까? -> NO
atomic은 하나의 변수에 대해 원자적 연산을 보장하지만, 여러 변수 또는 STL 컨테이너 같은 복잡한 데이터 구조에 대해서는 문제가 발생할 수 있습니다.atomic만으로 해결할 수 없습니다.atomic으로 모든 연산을 처리하려 하면 성능 저하가 발생합니다.// STL 컨테이너들은 멀티스레드 환경에서 동시 접근 시 크래시가 발생
vector<int> v;
v.push_back(1); // 멀티스레드 환경에서 안전하지 않음
std::vector)는 멀티스레드 안전하지 않습니다.push_back 같은 함수를 호출하면 내부 데이터가 손상되거나 크래시가 발생합니다.// 벡터의 크기를 미리 reserve 하면 메모리 재할당은 방지 가능
v.reserve(100000);
std::vector는 크기가 초과될 경우 내부적으로 새로운 메모리를 할당하고 데이터를 복사합니다(재할당).reserve를 사용하여 벡터 크기를 미리 확장하면 재할당 빈도를 줄일 수 있습니다.mutex(Mutual Exclusive): 상호 배타적 접근을 보장하는 락.mutex 사용mutex m; // 뮤텍스 선언
vector<int> v;
void Push()
{
for (int i = 0; i < 10000; i++)
{
m.lock(); // 자원 잠금
v.push_back(i); // 공유 자원 접근
m.unlock(); // 자원 해제
}
}
m.lock():Push 함수 내부의 코드 블록을 잠급니다.v.push_back에 접근하지 못하도록 제한합니다.m.unlock():template<typename T>
class LockGuard
{
public:
LockGuard(T& m) : _mutex(m) { _mutex.lock(); }
~LockGuard() { _mutex.unlock(); }
private:
T& _mutex;
};
lock(), 소멸자에서 unlock()을 호출하여 락과 언락을 자동화합니다.void Push()
{
for (int i = 0; i < 10000; i++)
{
LockGuard<mutex> lockGuard(m); // 락 자동 관리
v.push_back(i);
}
}std::lock_guard)void Push()
{
for (int i = 0; i < 10000; i++)
{
std::lock_guard<mutex> lockGuard(m); // 표준 락 가드 사용
v.push_back(i);
}
}
std::lock_guard:void Push()
{
for (int i = 0; i < 10000; i++)
{
std::unique_lock<mutex> uniqueLock(m, std::defer_lock); // 락을 뒤로 미룸
uniqueLock.lock(); // 특정 시점에서 락
v.push_back(i);
uniqueLock.unlock(); // 명시적으로 해제
}
}
std::unique_lock:std::defer_lock:lock()을 호출합니다.int main()
{
v.reserve(100000); // 벡터 크기 사전 예약
thread t1(Push); // 스레드 1 실행
thread t2(Push); // 스레드 2 실행
thread t3(Push); // 스레드 3 실행
t1.join(); // 스레드 1 종료 대기
t2.join(); // 스레드 2 종료 대기
t3.join(); // 스레드 3 종료 대기
cout << v.size() << endl; // 벡터 크기 출력
}
v.size()는 정확히 10000 * 3 = 30000이어야 합니다.