once_flag和call_once解决多线程中只使用一次的操作

在多线程的代码中,有时候有些操作只需要执行一次,比如fstream中打开文件open()

首先是不使用once_flag解决这种问题的缺陷和改进

1.示例代码的问题

#include<iostream>
#include<thread>
#include<mutex>
#include<string>
#include<fstream>
class LofFile
{
public:
	LofFile() {
		f.open("log.txt");
	}

	void share_print(std::string msg, int id) {
		std::lock_guard<std::mutex> guard(m_mutex);
		f << msg << " " << id << std::endl;
	}
	~LofFile() {
		f.close();
	}

private:
	std::mutex m_mutex;
	std::ofstream f;
};

void fun1(LofFile& log)
{
	for (int i = 0; i < 50; i++)
	{
		log.share_print("fun1 id", i);
	}
}

int main(int argc, char** argv)
{
	LofFile log;
	std::thread t1(fun1, std::ref(log));
	for (int i = 0; i < 50; i++)
	{
		log.share_print("main id", i);
	}
	if (t1.joinable()) {
		t1.join();
	}
	return 0;
}

这段代码中如果稍加改进,可以在多处创建LofFile的实例,那么如何确保log.txt,不被多次打开,那么我们看下这段代码的改进,以及其中的问题。

改进1:在使用share_print时判断文件是否打开

class LofFile
{
public:
	void share_print(std::string msg, int id) {
		if (!f.is_open()) {
			f.open("log.txt");
		}
		std::lock_guard<std::mutex> guard(m_mutex);
		f << msg << " " << id << std::endl;
	}
private:
	std::mutex m_mutex;
	std::ofstream f;
};

这种方法存在的问题是如果多个线程都同时进入if (!f.is_open()){//....},那么还是会出现重复打开情况,不安全的。

改进2:在open()前加上一次锁

class LofFile
{
public:
	void share_print(std::string msg, int id) {
		if (!f.is_open()) {
			std::unique_lock<std::mutex> locker(m_mutex_open_once, std::defer_lock);
			f.open("log.txt");
		}
		std::lock_guard<std::mutex> guard(m_mutex);
		f << msg << " " << id << std::endl;
	}
private:
	std::mutex m_mutex;
	std::mutex m_mutex_open_once;
	std::ofstream f;
};

这样改动看似没有问题,但是如果有两个以上的线程都if (!f.is_open()){//....},虽然在一个线程在open()的时候是锁住的,但是如果创建成功后
其他进入if的线程人可以进行到后面的部分,主要的问题是在is_open()这个函数应该加上锁,而不是open()函数。

改进3:在is_open()函数前加上锁

class LofFile
{
public:
	void share_print(std::string msg, int id) {
		{
			std::unique_lock<std::mutex> locker(m_mutex_open_once, std::defer_lock);
			if (!f.is_open()) {
				f.open("log.txt");
			}
		}

		std::lock_guard<std::mutex> guard(m_mutex);
		f << msg << " " << id << std::endl;
	}
private:
	std::mutex m_mutex;
	std::mutex m_mutex_open_once;
	std::ofstream f;
};

这里就可以了

4. 使用once_flag和call_once解决这个问题

class LofFile
{
public:
	void share_print(std::string msg, int id) {
		std::call_once(open_flag, [&] {f.open("log.txt"); });
		std::lock_guard<std::mutex> guard(m_mutex);
		f << msg << " " << id << std::endl;
	}
private:
	std::mutex m_mutex;
	std::once_flag open_flag;
	std::ofstream f;
};

这里的once_flag 就是创建只使用一次的mutex,call_once()函数这里使用一个lamda函数,就是用于打开文件的函数。

由此就解决了上面描述的问题。

posted @ 2020-08-02 16:49  cyssmile  阅读(373)  评论(0编辑  收藏  举报