在多线程的代码中,有时候有些操作只需要执行一次,比如fstream中打开文件open()
首先是不使用once_flag解决这种问题的缺陷和改进
1.示例代码的问题
#include
#include
#include
#include
#include
class LofFile
{
public:
LofFile() {
f.open("log.txt");
}
void share_print(std::string msg, int id) {
std::lock_guard 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 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 locker(m_mutex_open_once, std::defer_lock);
f.open("log.txt");
}
std::lock_guard 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 locker(m_mutex_open_once, std::defer_lock);
if (!f.is_open()) {
f.open("log.txt");
}
}
std::lock_guard 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 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函数,就是用于打开文件的函数。
由此就解决了上面描述的问题。