c++多线程系列
c++多线程thread操作(五)unique_lock加锁
c++多线程thread操作(七)父进程获取子进程变量的结果
(终)c++多线程thread操作(十)多线程并行实现数据相加的和
(一)加锁的三种方式:
1. mutex原生:
mutex<int>mu;
mu.lock();
// ...执行代码段
mu.unlock();
优点:可以解决数据竞争;
缺点:如果执行代码段抛出异常,mu锁将永远被锁住,无法释放;
2. lock_guard加锁
mutex<int>mu;
lock_guard<mutex> guard(mu);// guard析构时自动释放锁
// ...执行代码段
优点:可以解决mutex原生下的异常问题,guard析构时会自动释放mu锁;
缺点:lock_guard只能使用一次,无法多次使用;不够灵活
3. unique_lock
unique_lock<mutex> locker(m_mutex,defer_lock); // defer_lock 告诉locker m_mutex没有被锁住
locker.lock();
// ....程序段1
locker.unlock();
locker.lock();
// ....程序段2
locker.unlock();
优点:弹性更好的lock和unlock
缺点:需要更多的计算机性能(相比Lock_guard)
Lock_guard | unique_lock |
不能复制和move | 不能复制,可以move |
只能加锁解锁一次 | 可以加锁解锁多次 |
较少计算资源 | 较多计算资源 |
(二)实例
1. plan1
class LofFile {
private:
mutex m_mutex;
ofstream f;
public:
LofFile() {
f.open("log.txt");
}
void shared_print(string id, int value) {
unique_lock<mutex> locker(m_mutex,defer_lock);
f << "--1--From" << id << " : " << value << endl;
}
};
问题:f.open("log.txt")不应该创建类时就使用,应该在shared_print函数需要时使用;
解决方案:将f.open("log.txt")移动到shared_print函数中;
2. plan2
class LofFile {
private:
mutex m_mutex;
mutex m_mutex_open;
ofstream f;
public:
LofFile() {}
void shared_print(string id, int value) {
if (!f.is_open()) { // 保证只有shared_print调用才打开文件
unique_lock<mutex> locker(m_mutex_open, defer_lock); // 不是线程安全
f.open("log.txt");
}
unique_lock<mutex> locker(m_mutex,defer_lock);
f << "--1--From" << id << " : " << value << endl;
}
};
文件应保证只打开一次,如果文件没有打开,则先对文件加锁,然后再打开该文件;
但程序仍然不是线程安全的,如果A和B线程同时进入第10行,并且都发现文件没有打开,且都没有加锁,然后A和B都进入到11行,A和B都打开了文件,使得文件被打开2次。
解决方案:需要将第9行的if 语句都加上互斥,这样保证只打开一次;
3.plan3
class LofFile {
private:
mutex m_mutex;
mutex m_mutex_open;
ofstream f;
public:
LofFile() {}
void shared_print(string id, int value) {
unique_lock<mutex> locker(m_mutex_open, defer_lock); // 保证线程安全,但是每一次都要加锁,性能低,解决方案见下
if (!f.is_open()) {f.open("log.txt");}
unique_lock<mutex> locker(m_mutex,defer_lock);
f << "--1--From" << id << " : " << value << endl;
}
};
这时可以保证线程安全,但是每一访问shared_print都要先加锁,加锁操作相比执行操作是更耗时的,显然会使得效率低下;
解决方案:用once_flag解决
4. plan4
class LofFile {
private:
mutex m_mutex;
once_flag m_flag;
ofstream f;
public:
LofFile() {}
void shared_print(string id, int value) {
call_once(m_flag, [&]() {f.open("log.txt"); });
unique_lock<mutex> locker(m_mutex,defer_lock); // 弹性更好的lock和unlock
f << "--1--From" << id << " : " << value << endl;
}
};
once_flag定义访问一次标志,call_once表明只调用函数一次,并将调用后的结果输出到m_flag中;
5. 完整代码
#include <iostream>
#include <thread>
#include <string>
#include <mutex>
#include <fstream>
using namespace std;
class LofFile {
private:
mutex m_mutex;
once_flag m_flag;
ofstream f;
public:
LofFile() {}
void shared_print(string id, int value) {
call_once(m_flag, [&]() {f.open("log.txt"); });
unique_lock<mutex> locker(m_mutex);
f << "From" << id << " : " << value << endl;
}
};
void func_1(LofFile&log) {
for (int i = 0; i > -100; i--) {
log.shared_print("From t1: ", i);
}
}
int main() {
LofFile log;
thread t1(func_1,ref(log));
for (int i = 0; i < 100; i++) {
log.shared_print("From main: ", i);
}
t1.join();
return 0;
}