이벤트 + 락 = Eventable Lock 클래스
스레드를 동기화할 때는 다음 형식을 매우 흔히 볼 수 있다.
스레드1
- 락을 건다.
- 큐에 넣는다.
- 락을 풀고 신호를 보낸다.
스레드2
- 신호를 기다린다.
- 락을 건다.
- 큐에서 꺼낸다.
- 락을 푼다.
C++11로는 흔히 mutex
, condition_variable
을 lock_guard
, unique_lock
과 함께 사용해 처리하면 되며 보통 다음과 같다.
1 2 3 4 5 6 7 8 9 10 11 |
// mutex mtx // condition_variable cv; void thread1() { lock_guard<mutex> guard(mtx); // 큐에 넣음 cv.notify_one(); } |
1 2 3 4 5 6 7 8 9 10 |
// mutex mtx // condition_variable cv; void thread2() { unique_lock<mutex> guard(mtx); cv.wait(guard); // 큐에서 꺼냄 } |
이때 동기화에 사용하는 mutex
와 condition_variable
은 두 스레드에서 공유해야 한다. 워낙 흔하게 쓰는 형식인데다 여러 객체를 만들어 공유하는 것은 조금 귀찮으므로 신호를 보낼 수 있는 락 객체를 하나 만들면 편하다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
#pragma once #include <condition_variable> #include <mutex> namespace surp_lib { class EventableLock { public: enum class InitialLock {unlock, lock}; explicit EventableLock(InitialLock lock = InitialLock::lock); ~EventableLock() = default; EventableLock(EventableLock const&) = delete; EventableLock& operator=(EventableLock const&) = delete; void lock(); void unlock(); void set(); void wait(); private: std::condition_variable condition_; std::mutex mutex_; std::unique_lock<std::mutex> guard_; }; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
#include "eventable_lock.h" namespace surp_lib { EventableLock::EventableLock(InitialLock lock) : guard_{mutex_} { if (InitialLock::unlock == lock) unlock(); } void EventableLock::lock() { if (!guard_.owns_lock()) guard_.lock(); } void EventableLock::unlock() { if (guard_.owns_lock()) guard_.unlock(); } void EventableLock::set() { condition_.notify_one(); } void EventableLock::wait() { condition_.wait(guard_); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
#include <iostream> #include <thread> #include "eventable_lock.h" using namespace std::chrono_literals; using std::cout; using std::this_thread::sleep_for; using std::thread; using surp_lib::EventableLock; void thread1(EventableLock& lock) { cout << "Waiting in the thread\n"; lock.wait(); cout << "Processing in the thread\n"; sleep_for(3s); lock.unlock(); } int main() { EventableLock lockObj; cout << "Create a thread\n"; thread t1(thread1, std::ref(lockObj)); sleep_for(2s); cout << "Signal to the thread\n"; lockObj.set(); t1.join(); cout << "Program end\n"; return 0; } |
특별한 내용은 없지만 eventable_lock_test.cpp
에서 21번 줄에 주의한다. unlock()
을 하지 않으면 GCC와 달리 Visual Studio에서는 디버그 모드로 실행할 때 unlock of unowned mutex
메시지와 함께 프로그램을 바로 종료한다. 이는 Wrong message “unlock of unowned mutex”란 글에서 볼 수 있는 것처럼 호출하는 스레드에 해당 뮤텍스의 소유권이 있어야 하기 때문이다. 즉 해당 오류는 main()
을 종료할 때 발생하는데 thread1()
에서 wait()
이후 해당 뮤텍스 소유권을 유지하며, main()
을 종료할 때는 주 스레드에 소유권이 없는 상태로 해제 시도하기 때문이다.