once_flag和call_once解決多執行緒中只使用一次的操作
阿新 • • 發佈:2020-08-02
在多執行緒的程式碼中,有時候有些操作只需要執行一次,比如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函式,就是用於開啟檔案的函式。
由此就解決了上面描述的問題。