Singleton Thread Safe

2020-02-21

最近重构rdsn代码,创建了一个logger_proxy类,发现一些单例的线程安全问题,记录如下。

一、懒汉式

最初通过继承singleton的方式来实现的单例,如下:

class logger_proxy : public singleton<logger_proxy>
{
}

而singleton使用的是静态变量来实现的懒汉式单例:

template <typename T>
class singleton
{
public:
    singleton() = default;
 
    // disallow copy and assign
    singleton(const singleton &) = delete;
    singleton &operator=(const singleton &) = delete;
 
    static T &instance()
    {
        static T _instance;
        return _instance;
    }
};

在运行dsn_nfs_test的时候,当主线程退出时,会子线程会接收到SIGSEGV(signal id = 11),所以子线程会执行第26行的代码,打印该warn日志。

void native_linux_aio_provider::get_event()
{
    struct io_event events[1];
    int ret;
 
    task::set_tls_dsn_context(node(), nullptr);
 
    const char *name = ::dsn::tools::get_service_node_name(node());
    char buffer[128];
    sprintf(buffer, "%s.aio", name);
    task_worker::set_name(buffer);
 
    while (true) {
        if (dsn_unlikely(!_is_running.load(std::memory_order_relaxed))) {
            break;
        }
        ret = io_getevents(_ctx, 1, 1, events, NULL);
        if (ret > 0) // should be 1
        {
            dassert(ret == 1, "io_getevents returns %d", ret);
            struct iocb *io = events[0].obj;
            complete_aio(io, static_cast<int>(events[0].res), static_cast<int>(events[0].res2));
        } else {
            // on error it returns a negated error number (the negative of one of the values listed
            // in ERRORS
            dwarn("io_getevents returns %d, you probably want to try on another machine:-(", ret);
        }
    }
}

但是打印该日志,访问到logger_proxy的instance的时候,发生了coredump。也就是说,这时候的instance已经被销毁了

说明这时候的单例并不是真正的线程安全的。正常来说c11可以保证static是线程安全的,具体原因未知

二、饿汉式

把该单例代码修改成饿汉式:

class logger_proxy : public logger
{
public:
    logger_proxy() = default;
    virtual ~logger_proxy(void) = default
 
private:
    static std::unique_ptr<logger_proxy> _instance;
};
 
 
std::unique_ptr<logger_proxy> logger_proxy::_instance =  make_unique<logger_proxy>();

再去执行该dsn_nfs_test的时候,就不再发生coredump了

说明这种饿汉式才是真正的线程安全。

TODO:调研为什么懒汉式里的instance会被销毁。