C++ 的双重检查锁有什么潜在问题吗?

这是用于演示的简单代码片段。

有人告诉我,双重检查锁不正确。由于变量是非易失性的,编译器可以自由地重新排序调用或优化它们(有关详细信息,请参阅 codereview.stackexchange.com/a/266302/226000)。

但是我真的看到很多项目中确实使用了这样的代码片段。有人可以对这个问题有所了解吗?我用谷歌搜索并和我的朋友谈论它,但我仍然找不到答案。

#include <iostream>
#include <mutex>
#include <fstream>

namespace DemoLogger
{
    void InitFd()
    {
        if (!is_log_file_ready)
        {
            std::lock_guard<std::mutex> guard(log_mutex);
            if (!is_log_file_ready)
            {
                log_stream.open("sdk.log", std::ofstream::out | std::ofstream::trunc);
                is_log_file_ready = true;
            }
        }
    }


    extern static bool is_log_file_ready;
    extern static std::mutex log_mutex;
    extern static std::ofstream log_stream;
}

//cpp
namespace DemoLogger
{
    bool is_log_file_ready{false};
    std::mutex log_mutex;
    std::ofstream log_stream;
}

更新:谢谢大家。InitFd()确实有更好的实现,但它确实只是一个简单的演示,我真正想知道的是,双重检查锁定是否存在任何潜在问题。

有关完整的代码片段,请参阅https://codereview.stackexchange.com/questions/266282/c-logger-by-template。

回答

双重检查的锁是不正确的,因为它is_log_file_ready是一个普通的bool,并且这个标志可以被多个线程访问,其中一个是写者——这是一场比赛

简单的解决方法是更改​​声明:

std::atomic<bool> is_log_file_ready{false};

然后,您可以进一步放松对 的操作is_log_file_ready

void InitFd()
{
if (!is_log_file_ready.load(std::memory_order_acquire))
{
std::lock_guard<std::mutex> guard(log_mutex);
if (!is_log_file_ready.load(std::memory_order_relaxed))
{
log_stream.open("sdk.log", std::ofstream::out | std::ofstream::trunc);
is_log_file_ready.store(true, std::memory_order_release);
}
}
}

但总的来说,除了低级实现之外,应该避免双重检查锁定。

正如 Arthur P. Golubev 所建议的那样,C++ 提供了执行此操作的原语,例如 std::call_once

更新:

这是一个示例,显示了比赛可能导致的问题之一。

#include <thread>
#include <atomic>
using namespace std::literals::chrono_literals;
int main()
{
int flag {0}; // wrong !
std::thread t{[&] { while (!flag); }};
std::this_thread::sleep_for(20ms);
flag = 1;
t.join();
}

sleep是为了给线程一些时间来初始化。
这个程序应该立即返回,但编译完全优化-O3,它可能不会。这是由有效的编译器转换引起的,它将 while 循环更改为如下所示:

if (flag) return; while(1);

如果标志(仍然)为零,这将永远运行(将flag类型更改为std::atomic<int>将解决此问题)。

这只是未定义行为的影响之一,编译器甚至不必将更改提交flag到内存。

由于竞争或错误设置(或丢失)障碍,操作也可能被重新排序,从而导致不必要的影响,但这些不太可能发生,X86因为它是一个通常比较弱的架构更宽容的平台(尽管重新排序的影响确实如此)存在于X86)


以上是C++ 的双重检查锁有什么潜在问题吗?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>