c++多线程thread操作(五)unique_lock加锁

本文深入探讨了C++多线程编程中的加锁策略,包括mutex原生、lock_guard和unique_lock的优缺点,通过实例展示了如何在多线程环境中确保线程安全,避免数据竞争和死锁,特别关注了文件操作中的加锁问题。

c++多线程系列

c++多线程thread操作(一)环境部署

c++多线程thread操作(二)线程管理

c++多线程thread操作(三)数据竞争互斥访问

c++多线程thread操作(四)死锁的问题和解决

c++多线程thread操作(五)unique_lock加锁

c++多线程thread操作(六)条件变量的使用

c++多线程thread操作(七)父进程获取子进程变量的结果

c++多线程thread操作(八)父线程传递数据给子线程

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_guardunique_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;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值