-
C++ SpinLock구현C++/C++ 멀티스레드 2022. 10. 9. 13:44728x90
SpinLock은 앞선 포스팅에서 말한 존버메타 lock이다.
즉, lock을 다른 스레드가 사용중이면, 그 앞에서 lock이 풀릴때까지 계속 기다리는 것이다.
이거를 한 번 구현해보자.
- 들어가기에 앞서, 멀티스레드 프로그래밍을 할 때는, 내가 작성한 코드를 여러 스레드가 동시에 실행한다는 것을 잊지 말아야 한다. 그러지 않으면, 왜 에러가 나는지, 왜 코드가 내가 생각한대로 되지않는지 이해하기 어려워진다.
#include <iostream> #include <thread> #include <atomic> #include <mutex> class SpinLock { public: void lock() { while(_locked){ } _locked = true; } void unlock() { _locked = false; } private: bool _locked = false; }; int sum = 0; SpinLock spinLock; void Add() { for (int i = 0; i < 10000; i++) { lock_guard<SpinLock> guard(spinLock); sum++; } } void Sub() { for (int i = 0; i < 10000; i++) { lock_guard<SpinLock> guard(spinLock); sum--; } } void main() { std::thread t1(Add); std::thread t2(Sub); t1.join(); t2.join(); cout << sum << endl; }
가장 기본적으로 생각해볼 수 있는 방식이다.
어떤 스레드가 _locked를 사용한다면 _locked는 true가 되고,
다른 스레드는 while(_locked)에서 막혀서 대기하는 상황인 것이다.
그리고, lock을 사용하던 스레드가 unlock을 통해서 _locked를 false로 바꿔주면,
while문에서 기다리던 스레드들이 lock을 가질수 있는 아주 논리적이고 이성적인 방법으로 보인다.
하지만 실행해보면 시궁창이다
문제점을 분석해보자.
- 프로그램이 시작된다.
- 스레드 t1과 t2가 lock을 얻기 위해서 달려간다.
- 최초 _locked는 false이므로, 먼저 도착하는 스레드는 while문에서 막히지 않고 통과할 수 있을 것 같다.
- 하지만, 두 개의 스레드가 동시에 while문에 도착할 수도 있다.
- 두 개의 스레드 모두, _locked=true를 실행하기 전이라면, 모두 while문을 통과해서 _locked=true를 실행할 수 있다.
- 그러면 두 스레드 모두 lock을 얻게 된다.
어떻게 해결해야 할까?
저번에 알아봤던 atomic에서 아이디어를 얻을 수 있다.
while(_locked)를 검사하는 부분과
_locked=true를 실행하는 부분을 한 번에 실행해야 하면 된다.
이것과 관련된 함수는 atomic변수를 사용하면 제공된다.
_locked를 atomic변수로 선언하고, compare_exchange_strong함수를 사용하면 된다.
일단 코드부터 보고, 분석해보자.
class SpinLock { public: bool expected = false; bool desired = true; void lock() { while(_locked.compare_exchange_strong(expected, desired) == false) { expected = false; } } void unlock() { _locked.store(false); } private: atomic<bool> _locked = false; }; int sum = 0; SpinLock spinLock; void Add() { for (int i = 0; i < 10000; i++) { lock_guard<SpinLock> guard(spinLock); sum++; } } void Sub() { for (int i = 0; i < 10000; i++) { lock_guard<SpinLock> guard(spinLock); sum--; } }
이렇게 수정해주면 되는데
_locked.compare_exchange_strong함수를 보자.
이 함수가 하는 일은 다음과 같다.
- _locked==expected라면, _locked=desired를 수행하고, true를 반환한다.
- _locked!=expected라면, false를 반환한다.
우리는 다른 스레드가 lock을 갖고 있는 동안은 while문을 계속 돌며 대기하길 바란다.
따라서 우리가 기대하는 값(expected)은 lock을 아무도 갖고있지 않은 false인 상황이고,
lock을 아무도 갖고 있지 않다면, 그 lock을 desired값. 즉, true로 설정하여 lock을 갖게 되는 것이다.
이 함수를 의사코드로 작성해보면 다음과 같다.
//lock을 가질 수 있는 상황 if (_locked == expected) { expected = _locked; _locked = desired; return true; } //다른 스레드가 lock을 가지고 있는 상황. else { expected = _locked; return false; }
(나도 배운 코드인데, 사실 expected = _locked를 수행하는 이유는 모르겠다. 이 라인이 없다면,
while문을 돌며, expected=false를 계속 해줄 필요도 없을텐데)
여튼.. 이렇게 spinlock을 구현하면, 기존의 C++에서 제공하는 mutex를 사용하는 것과 동일하게 lock이 잘 작동한다.
728x90'C++ > C++ 멀티스레드' 카테고리의 다른 글
C++ 멀티스레드 동기화, 이벤트 사용법 (0) 2022.10.12 C++, Sleep을 이용한 Lock구현 (0) 2022.10.10 멀티스레드 Lock구현 (0) 2022.10.09 C++, 멀티스레드 교착상태(DeadLock) (0) 2022.10.08 C++ 멀티스레드, lock(mutex) (1) 2022.10.06