c++多线程thread操作(五)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;
}

 

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++中,`std::unique_lock`是一个灵活的互斥量封装类,用于处理多线程同步问题。它提供了更多的灵活性和功能,比起`std::lock_guard`更常用。 以下是一个使用`std::unique_lock`解决多线程问题的示例: ```cpp #include <iostream> #include <thread> #include <mutex> std::mutex mtx; // 定义一个互斥量 void printMessage(const std::string& message) { std::unique_lock<std::mutex> lock(mtx); // 创建一个unique_lock并锁住互斥量 for (int i = 0; i < 5; ++i) { std::cout << message << std::endl; } lock.unlock(); // 解锁互斥量 } int main() { std::thread t1(printMessage, "Thread 1"); std::thread t2(printMessage, "Thread 2"); t1.join(); t2.join(); return 0; } ``` 在上述示例中,我们定义了一个全局的`std::mutex`对象`mtx`作为互斥量。然后,我们创建了两个线程`t1`和`t2`,并且每个线程调用`printMessage`函数来输出一定次数的消息。 在`printMessage`函数中,我们首先创建了一个`std::unique_lock<std::mutex>`对象`lock`,并且通过构造函数将互斥量`mtx`作为参数传递进去。这样,`lock`对象会自动锁住互斥量,确保只有一个线程能够进入临界区。 在临界区内,我们输出了一定次数的消息。由于互斥量的保护,每个线程会按照顺序输出自己的消息,避免了竞争条件。最后,我们调用`lock.unlock()`手动解锁互斥量。 在`main`函数中,我们创建了两个线程并启动它们,然后通过`join()`函数等待线程结束。 通过使用`std::unique_lock`,我们可以更灵活地控制互斥量的锁定和解锁时机,并且提供了更多的同步操作,比如条件变量等。这样可以有效地处理多线程之间的同步和竞争条件问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值