1. 程式人生 > 實用技巧 >once_flag和call_once解決多執行緒中只使用一次的操作

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函式,就是用於開啟檔案的函式。

由此就解決了上面描述的問題。