Lock 기초

·2023년 12월 20일
0
vector<int32> v;

void Push()
{
	for (int32 i = 0; i < 10000; i++)
	{
		v.push_back(i);
	}
}

int main()
{	
	std::thread t1(Push);
	std::thread t2(Push);

	t1.join();
	t2.join();

	cout << v.size() << endl;
}

이렇게 하면 바로 터짐
vector는 공간이 부족해지면 더 큰 공간을 만들고 데이터를 옮김
멀티 스레드 환경은 다른 스레드를 기다려주지 않음
(이전에 Add, Sub에서 확인한 것 처럼)

그러니까 t1 쓰레드에서 공간이 부족해서 기존 공간을 줄이고 공간을 늘려야지 할 때 t2도 기존 공간을 지우고 늘려야지 라고 한다면 같은 공간을 두 번 지우게 됨

그러면 미리 20000개 공간을 만들어주면 되는 거 아님?


int main()
{	
	**v.resize(20000);**
	std::thread t1(Push);
	std::thread t2(Push);

	t1.join();
	t2.join();

	cout << v.size() << endl;
}

이렇게 하면 터지지는 않는데 멀티 스레드 환경이니까 연산이 동시 다발적으로 일어나면서 같은 위치에 데이터가 여러 번 들어갈 수 있음 → 모든 공간을 사용하지 않고 원하는 결과가 나오지 않을 수 있음

atomic으로 쓰면 vector의 세부적인 기능들을 사용할 수 없게됨

atomic 넣자마자 난리난 vector 함수들..

한 스레드가 Push 함수를 쓰고 있을 때 다른 스레드가 Push함수를 사용하지 못하도록 Lock을 걸 수 있음

LOCK

#include <mutex>
#include <vector>>

vector<int32> v;
mutex m; // 일종의 자물쇠

void Push()
{
	for(int32 i = 0; i < 10000; i++)
	{
		// 결국 하나의 스레드만 들어갈 수 있는 싱글 스레드
		m.lock();
		v.push_back(i);
		m.unlock();
	}
}

int main()
{
	std::thread t1(Push);
	std::thread t2(Push);

	t1.join();
	t2.join();
}

근데 락을 for문 전에 걸고 for문 끝날 때 풀면 안되나?
→ GPT 한테 물어보니 더 안전한 방법이 for문 전 후에 거는거라고 함

근데 아마 이렇게 하면 다른 스레드들이 한 스레드가 해당 연산을 모두 수행할 때 까지 기다려야해서 강의에서는 for문 내에서 건 것 같음

⇒ 뒤에서 언급하셔서 여기 추가함

lock을 for문 위에서도 써도 된다 함 근데 범위를 크게 정하는 게 좋은 상황인지에 대해서 고민 해 볼 필요가 있다고 함

이렇게 수동적으로 모든 케이스에서 lock을 넣어주고 빼주는 것은 구림.. 휴먼 에러 발생할 가능성도 높고…

c++의 유명한 패턴 중 RAII라 있음(Resource Acquisition Is Initialization)

생성자에서 잠그고 소멸자에서 풀어주는 형태

template<typename T>
class LockGuard
{
public:
	LockGuard(T& m)
	{
		_mutex = &m;
		_mutex->lock();
	}
	~LockGuard(T& m)
	{
		_mutex->unlock();
	}
}
void Push()
{
	for (int32 i = 0; i < 10000; i++)
	{
		LockGuard<std::mutex> lockGuard(m); // 이런 형태로 하면 끝날 때 알아서 풀어주니까 좋음
		v.push_back(i);

		if (i == 5000)
			break;
	}
}

unique_lock

이거는 LockGuard처럼 lock을 걸어 주는 건데 lockGuard와 달리 lock 시점을 원하는 대로 조절할 수 있음

void Push()
{
	for (int32 i = 0; i < 10000; i++)
	{
		// LockGuard<std::mutex> lockGuard(m); // 이런 형태로 하면 끝날 때 알아서 풀어주니까 좋음
		std::unique_lock<std::mutex> uniqueLock(m, std::defer_lock);		
		uniqueLock.lock();
		v.push_back(i);

		if (i == 5000)
			break;
	}
}

0개의 댓글