Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/161.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++多线程单-这个代码有什么问题_C++_Multithreading_Singleton - Fatal编程技术网

C++多线程单-这个代码有什么问题

C++多线程单-这个代码有什么问题,c++,multithreading,singleton,C++,Multithreading,Singleton,obj是否必须是不稳定的?如果第一个线程创建了T实例,并且假设第二个线程在此之前已经在缓存中加载了带有0的obj,那么现代处理器缓存是否会失效,或者第二个线程是否可能使用obj的0值并第二次创建T?假设两个线程都在不同的内核中运行 另外,请让我知道使用static T*而不是static T作为单例数据可能出现的任何问题 getInstance很好,不需要使成员易变 然而,deleteInstance存在一个问题,如果从不同线程调用deleteInstance,即使在检查NULL之后,它也可能执

obj是否必须是不稳定的?如果第一个线程创建了T实例,并且假设第二个线程在此之前已经在缓存中加载了带有0的obj,那么现代处理器缓存是否会失效,或者第二个线程是否可能使用obj的0值并第二次创建T?假设两个线程都在不同的内核中运行

另外,请让我知道使用static T*而不是static T作为单例数据可能出现的任何问题

getInstance很好,不需要使成员易变

然而,deleteInstance存在一个问题,如果从不同线程调用deleteInstance,即使在检查NULL之后,它也可能执行双重删除

另一个问题是deleteInstance没有将obj重置为NULL,因此在删除之后,getInstance将返回一个悬空指针,而不是创建一个新对象。

getInstance很好,并且不需要使成员易变

然而,deleteInstance存在一个问题,如果从不同线程调用deleteInstance,即使在检查NULL之后,它也可能执行双重删除

另一个问题是deleteInstance不会将obj重置为NULL,因此在删除之后,getInstance将返回一个悬空指针,而不是创建一个新对象。

getInstance方法正在使用。双重检查锁定本质上是危险的,因为可能会根据编译器为该行生成代码的方式引入竞争条件:

template <typename T, typename Lock>
class Singleton
{
public:
static T * getInstance()
{
    if (obj == 0)
    {
        lock.lock();
        if (obj == 0)
        {
            obj = new T;
        }
        lock.unlock();
    }
    return obj;
}

void deleteInstance()
{
   delete obj;
   obj = 0;
}

private:
volatile static T * obj = 0;
static Lock lock; // some sort of mutex
Singleton();
~Singleton();
Singleton(const Singleton &);
Singleton & operator=(const Singleton &);
};
这条路线主要包括以下三个步骤:

分配T字节的大小。 在分配的空间中构造一个T对象。 将指向已分配空间的指针分配给obj。 问题是,编译器不需要生成按顺序执行这些步骤的代码。例如,它可以分配T字节的大小,然后分配obj,然后就地构造T。在该场景中,存在一个竞争条件,两个不同的线程可以构造一个新的T对象。另外,两个不同的线程可以尝试在同一位置构造一个T对象

见斯科特·梅耶斯和安德烈·亚历山德雷斯库

就deleteInstance而言,您可能不应该提供这样的函数,因为它允许您在其他线程仍在使用t对象时意外地删除该对象

编辑:避免双重检查锁定的getInstance实现是:

obj = new T;
getInstance方法正在使用。双重检查锁定本质上是危险的,因为可能会根据编译器为该行生成代码的方式引入竞争条件:

template <typename T, typename Lock>
class Singleton
{
public:
static T * getInstance()
{
    if (obj == 0)
    {
        lock.lock();
        if (obj == 0)
        {
            obj = new T;
        }
        lock.unlock();
    }
    return obj;
}

void deleteInstance()
{
   delete obj;
   obj = 0;
}

private:
volatile static T * obj = 0;
static Lock lock; // some sort of mutex
Singleton();
~Singleton();
Singleton(const Singleton &);
Singleton & operator=(const Singleton &);
};
这条路线主要包括以下三个步骤:

分配T字节的大小。 在分配的空间中构造一个T对象。 将指向已分配空间的指针分配给obj。 问题是,编译器不需要生成按顺序执行这些步骤的代码。例如,它可以分配T字节的大小,然后分配obj,然后就地构造T。在该场景中,存在一个竞争条件,两个不同的线程可以构造一个新的T对象。另外,两个不同的线程可以尝试在同一位置构造一个T对象

见斯科特·梅耶斯和安德烈·亚历山德雷斯库

就deleteInstance而言,您可能不应该提供这样的函数,因为它允许您在其他线程仍在使用t对象时意外地删除该对象

编辑:避免双重检查锁定的getInstance实现是:

obj = new T;

您不需要使变量易变。互斥锁操作通常由一条XCHG处理器指令实现,该指令至少在英特尔处理器上起到内存屏障的作用——它将强制从内存中读取变量,而不是从缓存中读取。

您不需要使变量变为易失性的。互斥锁操作通常由XCHG处理器指令实现,该指令至少在英特尔处理器上起到内存屏障的作用-它将强制从内存读取变量,不是从缓存中实现的。

这是一种可怕的实现单例的方式,而实现单例首先是一种值得怀疑的做法。。。。如果您使用的编译器支持C++11的相关部分,则应使用静态局部变量:

static T * getInstance()
{
    lock.lock();
    if (!obj)
    {
        try
        {
            obj = new T;
        }
        catch (...)
        {
            obj = NULL;
            lock.unlock();
            throw;
        }
    }
    lock.unlock();
    return obj;
}

另一种选择是添加一个函数来显式初始化全局变量,然后在启动任何新线程之前调用它。

这是一种可怕的实现单例的方法,而实现单例首先是一种有问题的做法。。。。如果您使用的编译器支持C++11的相关部分,则应使用静态局部变量:

static T * getInstance()
{
    lock.lock();
    if (!obj)
    {
        try
        {
            obj = new T;
        }
        catch (...)
        {
            obj = NULL;
            lock.unlock();
            throw;
        }
    }
    lock.unlock();
    return obj;
}
另一个选项是添加一个函数来显式初始化全局变量,然后在启动任何新线程之前调用该函数。

如果您有C++11,则将obj设置为原子函数,从而消除getInstance中的所有问题

但是deleteIn STANTE是危险的,因为类不知道是否有任何未完成的指针,因此调用此函数可能会将对象从某个无辜线程下拉出。

如果使用C++11,则将obj设为原子,getInstance中的所有问题都会消失



但是deleteInstance是危险的,因为类不知道是否有任何未完成的指针,因此调用此函数可能会将对象从某个无辜线程下拉出。

WTF此类是否有赋值运算符?如果它的工作做得很好,就永远不会有另一个实例可以分配。@cHao这就是为什么它是私有的,不允许复制…@Luchian:那么它不应该被删除吗?@cHao如果没有C++11支持,就不会。最大的“错误”是它是单例模板。这就好像你想拥有很多单体,而在实践中很少有这样的事情在C++项目中。WTF这个类有一个赋值运算符吗?如果它的工作做得很好,就永远不会有另一个实例可以分配。@cHao这就是为什么它是私有的,不允许复制…@Luchian:那么它不应该被删除吗?@cHao如果没有C++11支持,就不会。最大的“错误”是它是单例模板。这就好像你想拥有大量的单体,而在实践中,很少有这样的事情在C++项目中。你能详细说明为什么它不需要是易失性的吗?@ XVATAR,因为波动有不同的含义。它阻止一些优化以防止一些并发问题,它并不表示关键部分。您能否详细说明为什么它不需要是易失性的?@xvatar,因为易失性有不同的含义。它阻止了一些优化以防止一些并发性问题,它并不表示一个关键部分。@Daniel Trebbien-您能建议它需要如何改进吗written@Medicine:我在回答中添加了一个getInstance实现,避免了双重检查锁定。@DanielTrebbien-上面提到的lucas1024,我的代码没有双重检查问题,而且obj=new T在临界段内。您实现的方式更昂贵,我认为即使在第一次实例化singleton之后,您仍然希望锁定/解锁昂贵的组件。我收回我的评论-Daniel是对的!谢谢你的链接!双重检查锁定可以在C++11中工作,也可以使用编译器扩展@Daniel Trebbien-你能建议它需要怎样做吗written@Medicine:我在回答中添加了一个getInstance实现,它避免了双重检查锁定。@DanielTrebbien-正如上面lucas1024所提到的,我的代码没有双重检查问题,并且obj=new T在临界部分内。您实现的方式更昂贵,我认为即使在第一次实例化singleton之后,您仍然希望锁定/解锁昂贵的组件。我收回我的评论-Daniel是对的!谢谢你的链接!双重检查锁定可以在C++11中工作,也可以使用编译器扩展。即使有多个核心,每个核心都有一个缓存,你的答案是否正确?那么,更新是否会及时到达所有内核?有保证吗?@Medicine,是的,标记变量volatile基本上会禁用它的缓存。当然,这是非常昂贵的。@Medicine:只是澄清一下,volatile关键字本质上告诉编译器,其他未知的外部事物最终可能会在任何时间点改变变量的值。例如:硬件中断可能会在其中写入内容。您不需要它,因为每个进程都将使用互斥来获得对内存的独占访问。您的答案是否正确?即使有多个内核,每个内核都有一个缓存,对吗?那么,更新是否会及时到达所有内核?有保证吗?@Medicine,是的,标记变量volatile基本上会禁用它的缓存。当然,这是非常昂贵的。@Medicine:只是澄清一下,volatile关键字本质上告诉编译器,其他未知的外部事物最终可能会在任何时间点改变变量的值。例如:硬件中断可能会在其中写入内容。您不需要它,因为每个进程都将使用互斥来获得对内存的独占访问。
template <typename T, typename Lock>
std::atomic<T*> Singleton<T, Lock>::obj = 0;
T *tmp = 0;
obj.exchange(tmp);
delete tmp;