04_lock

04_lock

날짜
생성자
ShalomShalom
카테고리
pararell
작성일
2023년 05월 15일
태그
이전 블로그
c++
구조체나 동적할당된 자료를 동기화하기 위해 lock의 사용법을 알아본다.

예제코드

vector<i32> v; void push() { for (i32 i = 0; i < 100'0000; i++) { v.push_back(i); // 터짐 } } int main() { std::thread thread1(push); std::thread thread2(push); thread1.join(); thread2.join(); cout << v.size() << endl; }
일반적인 자료구조(컨테이너)인 경우는 멀티스레드 환경에서 동작하지 않는다. 이유는 다음과 같다.
  1. vector 같은 경우는 내부적으로 동적 배열의 형태이다.
  1. capacity가 초과되면 기존 포인터를 날리고, capacity가 더 증가한 동적배열의 주소가 값으로 할당이 된다.
  1. 이때 기존 포인터에 접근을 하면 값이 날라간 주소에 접근을 하거나, 또는 2번의 상황이 두 개의 스레드에서 동시에 발생을 하면 double free상황이 날 수 있다.
reserve를 한다하더라도 동시에 같은 인덱스에 push 할 수 있으므로 값 분실이 생길 수 있다.

Atomic을 이용하면 안되나?

atomic은 일반적인 데이터를 위한 자료구조이지, 세부적인 기능 호출이 필요한 클래스나 구조체를 지정할 수 없다.

Lock

기존 windows에서는 CreateCriticalSection()을 이용하였지만 c++11에선 코드의 통합을 위해 mutex라는 자료구조를 지원한다.
#include <mutex> vector<i32> v; mutex mut; // 상호배타적 void push() { for (i32 i = 0; i < 100'0000; i++) { mut.lock(); v.push_back(i); mut.unlock(); } } int main() { std::thread thread1(push); std::thread thread2(push); thread1.join(); thread2.join(); cout << v.size() << endl; }

단점

  • 위의 경우는 mutex.lock()은 하나의 thread에서 한번만 호출 할 수 있다,
  • 다른 스레드에서는 unlock이 될 때까지 기다려야하므로, 단일 스레드랑 동작이 다를바가 없어진다.
  • lock을 하고 unlock을 안하게 된다면 그 누구도 mutex에 접근할 수 없는 상황이 발생한다.

참고 사항

단점, 2번 같은 상황은 다음과 같이 방지 할 수 있다.
template<typename T> class LockGuard { public: LockGuard(T& m) { p_mutex = &m; p_mutex->lock(); } ~LockGuard() { p_mutex->unlock(); } private: T* p_mutex; }
위 코드는 표준으로 이미 lock_guard라는 api로 구현이 되어 있다.
mutex m; lock_guard<mutex> lg(m); // 이때 lock
unique_lock도 있다.
mutex m; unique_lock<mutex> ul(m, defer_lcok); ul.lock(); // 이때 lock

댓글

guest