本章学习重点:
Unique Lock
线程环境中处理初始化问题
前面介绍过两点"加锁"的方式:一个是使用mutex,另一个是lock_guard<std::mutex>,还有另外一种方式是使用unique_lock.
lock_guard 最大的缺点也是简单,没有给程序员提供足够的灵活度,因此,C++11 标准中定义了另外一个与 Mutex RAII 相关类 unique_lock,该类与 lock_guard 类相似,也很方便线程对互斥量上锁,但它提供了更好的上锁和解锁控制。
From <http://www.cnblogs.com/haippy/p/3346477.html>
黄棒清使用的一词——"弹性",来表示unique_lock的优点。比如在案例中cout后面执行其他代码,但是该代码不需要mutex被锁住,可是使用unlocker解锁。
std::unique_lock<std::mutex> locker(m_mutex); //std::lock_guard<mutex> locker(m_mutex); std::cout<< "from " << id << value << endl; locker.unlock(); //...... |
而unique_lock还有一个参数defer_lock,可以将该锁打开,在使用之前,直接lock一下就可以。
std::unique_lock<std::mutex> locker(m_mutex, defer_lock); //..... //std::lock_guard<mutex> locker(m_mutex); locker.lock();//locker这把锁锁上下面的cout,但上面//...... 中的其他代码并没有上锁 std::cout<< "from " << id << value << endl; locker.unlock(); //...... |
测试结果:
|
|
Note though, that the unique_lock object does not manage the lifetime of the mutex object in any way: the duration of the mutex object shall extend at least until the destruction of the unique_lock that manages it.
From <http://www.cplusplus.com/reference/mutex/unique_lock/>
std::unique_lock 移动(move assign)赋值操作
std::unique_lock 支持移动赋值(move assignment),但是普通的赋值被禁用了,
move (1) | unique_lock& operator= (unique_lock&& x) noexcept; |
copy [deleted] (2) | unique_lock& operator= (const unique_lock&) = delete; |
移动赋值(move assignment)之后,由 x 所管理的 Mutex 对象及其状态将会被新的 std::unique_lock 对象取代。
From <http://www.cnblogs.com/haippy/p/3346477.html>
Member types
member type | definition | description |
mutex_type | The template parameter (Mutex) | The managed mutex object type |
Member functions
Construct unique_lock (public member function )
Destroy unique_lock (public member function )
Locking/unlocking
Lock mutex (public member function )
Lock mutex if not locked (public member function )
Try to lock mutex during time span (public member function )
Try to lock mutex until time point (public member function )
Unlock mutex (public member function )
Modifiers
Move-assign unique_lock (public member function )
Swap unique locks (public member function )
Release mutex (public member function )
Observers
Owns lock (public member function )
Return whether it owns a lock (public member function )
Get mutex (public member function )
From <http://www.cplusplus.com/reference/mutex/unique_lock/>
#include <iostream> // std::cout #include <thread> // std::thread #include <mutex> // std::mutex, std::unique_lock
std::mutex mtx; // mutex for critical section
void print_block(int n, char c) { // critical section (exclusive access to std::cout signaled by lifetime of lck): std::unique_lock<std::mutex> lck(mtx); for (int i = 0; i<n; ++i) { std::cout << c; } std::cout << '\n'; }
int main() { std::thread th1(print_block, 5000, '*'); std::thread th2(print_block, 500, '$');
th1.join(); th2.join();
return 0; } |
图一:使用defer_lock,但没有lock时的结果
图二:使用cout之前locker这把锁锁上了cout的结果
Lazy Initialization
在上面的上面的例子中,f.open()在构造时就会被打开一次,而对于不需要的情况下再次的打开显然是浪费资源,因此,移动到下面的成员函数中:
void share_Print(std::string id, int value) { If(!f.is_open()) { f.open("log.txt"); } std::unique_lock<std::mutex> locker(m_mutex); //std::lock_guard<mutex> locker(m_mutex); std::cout<< "from " << id << value << endl; } |
但是,同cout一样,open也需要使用锁来保证线程的安全,
//……. Mutex m_mutex_open;
void share_Print(std::string id, int value) { If(!f.is_open()) {
std::unique_lock<std::mutex> locker(m_mutex_open); f.open("log.txt"); } std::unique_lock<std::mutex> locker(m_mutex,std::defer_lock); //std::lock_guard<mutex> locker(m_mutex); std::cout<< "from " << id << value << endl; } |
这种情况也同样不能保证线程的安全,这是由于is_open()函数没有使用锁来保护该资源,导致多个线程同时因为抢占is_open函数时而导致死锁的发生。进一步修改上述代码
#include<iostream> #include<string> #include<thread> #include<mutex> #include<fstream>
using namespace std;
std::mutex mu; class lofFile { public: lofFile() { //f.open("log.txt");//构造函数中打开log.txt文件 } void share_Print(std::string id, int value) { { std::unique_lock<std::mutex> locker(m_mutex1); if (!f.is_open()) { f.open("log.txt"); } }
std::unique_lock<std::mutex> locker(m_mutex);
//std::lock_guard<mutex> locker(m_mutex); std::cout<< "from " << id << value << endl; }
protected: private: mutex m_mutex; mutex m_mutex1; ofstream f; //被m_mutex保护的对象 };
void function_1(lofFile& l) { for (int i = 0; i > -100; i--) { //std::cout << "From t1:" << i << std::endl; //using shared_Print function instead of function_1 l.share_Print("From t1", i);
} } int main() { lofFile l; thread t1(function_1, std::ref(l)); for (int i = 0; i < 100; i++) { //cout << "From main:" << i << endl; l.share_Print("From Main()", i); } t1.join(); }
|
为了简洁起见,C++11委员会人员提供一种解决方案。使用lazy initialization
将上面的
mutex m_mutex1;
修改为std::once_flag m_flag;将锁和文件使用lambda来代替(能够保证一个线程只能被调用一次):
std::call_once(m_flag, [&]() {f.open("log.txt"); }); |
清单:使用lazy initializatin
#include<iostream> #include<string> #include<thread> #include<mutex> #include<fstream>
using namespace std;
std::mutex mu; class lofFile { public: lofFile() { //f.open("log.txt"); } void share_Print(std::string id, int value) { //{ //std::unique_lock<std::mutex> locker(m_mutex1, defer_lock); //if (!f.is_open()) //{ //f.open("log.txt"); //} //} //直接使用m_flag std::call_once(m_flag, [&]() {f.open("log.txt"); });
std::unique_lock<std::mutex> locker(m_mutex, defer_lock); //..... //std::lock_guard<mutex> locker(m_mutex); locker.lock();//locker这把锁锁上下面的cout,但上面//...... 中的其他代码并没有上锁 std::cout<< "from " << id << value << endl; locker.unlock(); //...... }
protected: private: mutex m_mutex; mutex m_mutex1; std::once_flag m_flag; ofstream f; //被m_mutex保护的对象 };
void function_1(lofFile& l) { for (int i = 0; i > -100; i--) { //std::cout << "From t1:" << i << std::endl; //using shared_Print function instead of function_1 l.share_Print("From t1", i);
} } int main() { lofFile l; thread t1(function_1, std::ref(l)); for (int i = 0; i < 100; i++) { //cout << "From main:" << i << endl; l.share_Print("From Main()", i); } t1.join(); }
|
测试结果: