@(C plus plus)[summary,lock] # Lock in C++11 ## Mutex Wrapper C++11提供了多种类型的mutex wrapper,主要有lock_guard,unique_lock和scoped_lock,shared_lock这几种。mutex很少直接使用,多是与mutex wrapper一起配合使用。mutex wrapper采用RAII机制,来确保锁能够是否,即使上锁期间发生了异常。 RAII机制在维基百科上有着比较好的解释: >Resource acquisition is initialization (RAII)is a programming idiom used in several object-oriented languages. In RAII, holding a resource is a class invariant, and is tied to object lifetime: **resource allocation (or acquisition) is done during object creation** (specifically initialization), by the constructor, **while resource deallocation (release) is done during object destruction (specifically finalization), by the destructor**. Thus the resource is guaranteed to be held between when initialization finishes and finalization starts (holding the resources is a class invariant), and to be held only when the object is alive. Thus if there are no object leaks, there are no resource leaks. ### lock_guard lock_guard是最简单的mutex wrapper,仅提供了`constructor`和`destructor`两个接口。但是并没有提供copy constructor。其提供的constructor形式如下: - explicit lock_guard( mutex_type& m ); - lock_guard( mutex_type& m, std::adopt_lock_t t ); - ~lock_guard() 由lock guard支持的成员函数,我们可以总结出其特点: - 仅能够在构造函数中加锁,在析构函数中释放锁,没有显示的lock或unlock接口。 - 仅能使用adop_lock_t 来继承已有的lock ownership。 典型的两个使用场景: #### 保证mutex正确释放 ```c++ int g_i = 0; std::mutex g_i_mutex; // protects g_i void safe_increment() { std::lock_guard<std::mutex> lock(g_i_mutex); ++g_i; std::cout << std::this_thread::get_id() << ": " << g_i << ' '; // g_i_mutex is automatically released when lock // goes out of scope } ``` #### 继承ownership来保证mutex释放 ```c++ struct bank_account { explicit bank_account(int balance) : balance(balance) {} int balance; std::mutex m; }; void transfer(bank_account &from, bank_account &to, int amount) { // 通过lock来保证不会发生死锁 std::lock(from.m, to.m); // 通过lock_guard来保证结束时能够正确释放lock std::lock_guard<std::mutex> lock1(from.m, std::adopt_lock); std::lock_guard<std::mutex> lock2(to.m, std::adopt_lock); from.balance -= amount; to.balance += amount; } ``` ###unique_lock unique_lock同样是一个mutex wrapper,但是在功能上要丰富得多,主要体现在以下几个方面: - allowing deferred locking, - time-constrained attempts at locking, - recursive locking, - transfer of lock ownership - use with condition variables #### deferred locking ```c++ struct Box { explicit Box(int num) : num_things{num} {} int num_things; std::mutex m; }; void transfer(Box &from, Box &to, int num) { //此时并没有真正获取锁 std::unique_lock<std::mutex> lock1(from.m, std::defer_lock); std::unique_lock<std::mutex> lock2(to.m, std::defer_lock); // lock both unique_locks without deadlock std::lock(lock1, lock2); from.num_things -= num; to.num_things += num; // 'from.m' and 'to.m' mutexes unlocked in 'unique_lock' dtors } ``` ### used for condition variable ```c+ std::mutex m; std::condition_variable cv; std::string data; bool ready = false; bool processed = false; void worker_thread() { // Wait until main() sends data std::unique_lock<std::mutex> lk(m); //此处只能使用unique_lock,因为unique_lock有对外的lock和unlock接口 //wait等待时会进行unlock。唤醒时会再次lock cv.wait(lk, []{return ready;}); // after the wait, we own the lock. std::cout << "Worker thread is processing data "; data += " after processing"; // Send data back to main() processed = true; std::cout << "Worker thread signals data processing completed "; // Manual unlocking is done before notifying, to avoid waking up // the waiting thread only to block again (see notify_one for details) lk.unlock(); cv.notify_one(); } int main() { std::thread worker(worker_thread); data = "Example data"; // send data to the worker thread { std::lock_guard<std::mutex> lk(m); ready = true; std::cout << "main() signals data ready for processing "; } cv.notify_one();