Lock에 대한 개념
- atomic의 경우 특정 변수 하나에 대해서는 thread-safe하게 처리가 가능하지만 특정 범위에 대해서는 thread-safe하게 처리하려면 다른 방법을 강구해야 한다.
Mutex (mutual exclusive) - 상호 배타적이라는 의미
- Lock을 사용할 수 있게 해주는 클래스
- thread-safe한 구간을 만들기 위해 해당 구간의 시작부분에 lock을 걸고, 끝 부분에 unlock을 해주면 된다.
데이터 영역에 존재하는 vector<int> v의 push_back을 멀티 스레드로 실행하면 어떤 일이 벌어질까?
결과는 크래쉬가 난다.
- 왜냐하면 push_back의 동작 원리는 데이터를 집어넣고 나서 size가 capacity 이상일 경우 메모리를 다시 할당하고 기존에 있던 데이터들을 복사하는 일련의 과정을 거치는데, 어떤 스레드가 이 작업을 하는 중 다른 스레드가 이 작업을 마치고 데이터를 지워버리는 상황이 발생할 수 있기 때문이다.
-> reserve로 공간을 미리 할당해주니 크래쉬는 해결되었으나 정확한 값이 나오지 않았다.
- 그렇다면 atomic<vector<int>>를 해주면 되는가? 이 또한 아니다.
- atomic은 여러 줄에 걸쳐 실행되는 명령어들에 대해 lock_xxx와 같은 명령어로 대체하여 중간 과정에 여러 쓰레드들이 개입하는 것을 방지하는 것인데, vector의 push_back은 내부적으로 힙 메모리 할당 해제, 값이 들어가야 할 자리를 찾고 그 자리에 값을 넣는 일련의 과정들이 있기 때문에 atomic을 사용하더라도 이 과정들을 원자적으로 처리할 수 없다.
-> atomic으로 감싸보니 static_assert failed가 뜨며 실패했다.
'atomic<T>가 복사 가능해야 하고, 복사 생성 가능해야 하고, 이동 생성 가능해야하고 ...'
위에서 언급했듯 vector는 복사나 이동에 복잡한 연산을 수행하기 때문에 원자적이지 않다 라는 결론으로 보여진다.
#include <iostream>
#include <thread>
#include <vector>
#include <windows.h>
#include <atomic>
#include <mutex>
using namespace std;
vector<int> v;
mutex m;
void Push()
{
for (int i = 0; i < 10000; ++i)
{
m.lock();
v.push_back(i);
m.unlock();
}
}
int main()
{
//ios_base::sync_with_stdio(false);
//cin.tie(NULL);
//cout.tie(NULL);
v.reserve(1000000);
thread t1(Push);
thread t2(Push);
t1.join();
t2.join();
cout << v.size() << endl;
return 0;
}
- mutex의 lock을 사용하니 정상적으로 20000이라는 값이 출력된다.
- 이렇게 한 번에 한 명씩 처리하는 것을 동기화라고 하며, 공유해서 사용하면 안되는 이런 영역을 임계영역이라고 한다.
그러면 그냥 공유 데이터마다 락을 잡으면 되는거 아니냐?
- 그렇지 않다. 왜냐하면 lock - 할일 - unlock 패턴으로 이루어져 있을 때 '할 일'에서 많은 시간을 지체하면 그만큼 뒤의 스레드들이 딜레이되기 때문이다.
그리고 lock/unlock은 항상 짝을 맞춰줘야 하는데 unlock을 하지 않고 해당 스코프를 탈출하면 그 락은 영원히 풀리지 않게 된다.
이 같은 상황을 방지하기 위해 RAII(객체가 스코프를 벗어나면 자동으로 자원을 해제해주는 기법)을 사용하게 된다.
(LockGuard 라는 클래스를 만들고 생성자에서 lock, 소멸자에서 unlock 해주는 원리)
이는 C++ 표준에 lock_guard라는 클래스로 구현되어 있고 unique_lock이라는 클래스는 생성자에서 바로 락을 거는게 아니라 락을 거는 시점을 뒤로 미룰 수 있다.
atomic과 lock을 알고 있다면 대부분의 공유자원에서 발생하는 문제는 대부분 해결할 수 있다.
'게임 서버 > [Inflearn_rookiss]올인원_클라&서버 연동' 카테고리의 다른 글
6. 데드 락 (0) | 2023.12.11 |
---|---|
5. 스핀 락 (0) | 2023.12.11 |
3. CPU 파이프라인 및 공유 자원 (0) | 2023.12.11 |
2. 멀티 쓰레드 실습 (0) | 2023.12.11 |
1. 멀티 쓰레드란? (0) | 2023.12.11 |
댓글