https://www.youtube.com/user/BoQianTheProgrammer video URL
Unique Lock
unique_lock and lock_guard similar, they are mutex of wrapper classes, but the former is more flexible
- lock_guard not unlock method, unique_lock can call unlock
- unique_lock can call the delay lock method, lock_guard not
- unique_lock not be copied, can be moved, not copied lock_guard nonremovable
// 1
...
std::unique_lock<std::mutex> locker(mu);
g_num ++;
locker.unlock();
...
// 2
...
std::unique_lock<std::mutex> locker(mu, std::defer_lock);
...
locker.lock();
g_num++;
locker.unlock();
...
locker.lock();
g_num++;
locker.unlock();
...
// 3
...
std::unique_lock<std::mutex> locker2 = std::move(locker);
unique_lock not substituted lock_guard
unique_lock flexibility comes at a cost, flexible space while their own share of the larger (do not understand on-time performance)
and sometimes do not need those unique_lock of flexibility, as long as you can lock_guard
Lasy Initialization
Look at the following piece of code
class LogFile {
std::mutex _mu;
ofstream _f;
public:
LogFile() {
_f.open("log.txt");
}
void func(string id, int v) {
std::unique_lock<mutex> locker(_mu);
_f << id << ", " << v << endl;
}
}
Class print log, open in the constructor log.txt
but there may never had the printed journal (call func), then there is no need to call _f.open
consider calling _f.open in func as follows
class LogFile {
std::mutex _mu;
ofstream _f;
public:
LogFile() {
// _f.open("log.txt");
}
void func(string id, int v) {
if (!_f.is_open()) {
_f.open("log.txt");
}
std::unique_lock<mutex> locker(_mu);
_f << id << ", " << v << endl;
}
}
Thread Safety
Then you must consider the security thread of LogFile :: func (for resource objects _f)), in accordance with the previous method, to control the use _f by mutex
class LogFile {
std::mutex _mu_open;
ofstream _f;
public:
LogFile() { }
void func(string id, int v) {
if (!_f.is_open()) {
std::unique_lock<mutex> locker(_mu_open);
_f.open("log.txt");
}
...
}
}
The code above do not thread-safe. Scenario assumes:
thread A and thread B to simultaneously if (_f.is_open())
, then the file is not opened, so if the judgment through,
then A, B will call acquires the mutex, suppose A first acquired, then A call to open the file open, and then released mutex,
then B will get mutex, and then will be called once again open.
Twice to open a file!
So not only _f.open need to be synchronized, _f.is_open also need to be synchronized, it is easy to think:
...
std::unique_lock<mutex> locker2(_mu_open);
if (_f.is_open()) {
_f.open("log.txt");
}
locker2.unlock();
...
But at this time and there will be another problem, func each time the function is called, there will be a mutex acquired behavior will affect the concurrency of the program to some extent
std::once_flag和std::call_once
For that question above, c ++ provides std :: once_flag to solve:
class LogFile {
std::once_flag _flag;
ofstream _f;
public:
LogFile() { }
void func(string id, int v) {
std::call_once(_flag, [&](){_f.open("log.txt");});
...
}
}
The above code of lambda function is a thread called only once!