线程工作者方法中的Singleton getInstance

时间:2008-11-07 12:40:03

标签: c++ multithreading

这个问题是关于在工作线程中使用单例对象的getter方法。首先是一些伪代码:

// Singleton class which contains data
class MyData
{
    static MyData* sMyData ;

    int  mData1[1024];
    int  mData2[1024];
    int  mData3[1024];

    MyData* getInstance()
    {
        // sMyData is created in the very beginning.
        return sMyData ;
    }

    void getValues(int idx, int& data1,int& data2,int& data3)
    {
        data1 = mData1[idx];
        data2 = mData2[idx];
        data3 = mData3[idx];
    }

    int* getData1()
    {
        return &mData1[0];
    }
}

class MyThread
{
    void workerMethod()
    {
        MyData* md = MyData::getInstance();

        int d1,d2,d3;
        md->getValue( 12, d1,d2,d3 );

        int* data1 = md->getData1();
        d1 = data1[34];
    }
}

现在你看到我有一些getter方法(所有只读),MyData :: getInstance(),MyData :: getValue()和MyData :: getData1()。第一个问题是这些方法的线程安全性如何?

由于它们通常被称为方法,因此我应该避免使用互斥锁来保护这些方法。

第二个问题是:在多线程应用程序中从中央源读取数据的建议方法是什么,尤其是在工作方法中。

谢谢!

5 个答案:

答案 0 :(得分:5)

如果没有其他线程会尝试写入单例对象中的数据,则不需要保护它们:根据定义,在没有编写器的情况下,多个读取器是线程安全的。这是一个常见的模式,程序的初始化代码设置一个单例,然后只能由工作线程读取。

但是,如果任何线程写入此数据而其他人正在从中读取数据,则必须以某种方式保护它。如果你有很多读者,只有偶尔的作家,那么值得考虑某种“读写”锁定,它允许多个读者在没有任何作者的情况下。

答案 1 :(得分:2)

无法判断这是否是线程安全的。如果数据在对象创建期间初始化,并且永远不会更改,则此操作将正确运行。如果你通过其他方法改变底层数据,那么读者将不得不对作者进行某种同步,没有办法解决这个问题。

根据您正在做的事情,您可能能够使用比互斥量更轻的东西,例如原子更新同步命令,或使用读写器锁,但不知道您正在做什么,这是不可能的告诉。

答案 2 :(得分:1)

你的方法没问题,但你有两个主要问题。

首先,MyData :: getInstance()应该是静态的,不会创建实例。为了最大限度地减少互斥锁的使用,您应该查看double checked lock

其次,线程安全取决于getter方法的调用者而不是MyData类。如果这就是你想要的,那就太好了。如果没有,那么你将不得不在MyClass中提出某种访问控制。此外,由于您包含的数据类型只是一个基本类型(int),因此在您的代码投入生产之前,您将永远不会看到任何同步问题。

答案 3 :(得分:1)

首先回过头来阅读这个问题以获得更好的singelton版本:
Can any one provide me a sample of Singleton in c++?

另请注意: NOT 使用单身作为美化的全球变量 它只会增加糟糕设计的复杂性。只需使用全局变量。

只要你只是从单身人士那里读书,它在使用时就是线程安全的。

唯一不是线程安全的点(语言无法保证)是在创建期间。所以从技术上讲,你应该在创建实例的部分周围添加锁,以保证在任何人都可以使用它之前完全创建了单例。

注意:不要使用双重检查锁定来诱使锁定策略优化。它可以 NOT 在C ++中正常工作。阅读此DDJ article

如果你能保证在单线程环境中(在创建任何线程之前)实例化单例(可能是第一次调用getInstance()),那么你可以在实例化期间免除锁的需要。

如果您更改代码以便其他线程可以写入单例,那么您需要考虑锁定和一致性。如果您的写入不是原子的,则只需要锁定。如果你的写只是更新一个整数,它已经是原子的,不需要改变。如果您的代码不是原子的:

  • 在方法中编写多个整数
  • 执行读取然后写入

然后,您需要锁定对象,直到所有写入完成,因此对具有读取权限的方法具有锁定,以阻止它们从对象读取,而另一种方法更新对象。

因此,这是成员变量只能通过成员方法访问的另一个原因。

答案 4 :(得分:0)

对于线程安全,您需要查看整个类。如上所述,您的类将不是线程安全的。虽然你的getValues方法没问题,但是getData1方法存在问题。

你说他们是(只读)getter方法。但是,两者都没有声明为const方法。 getData1作为const方法无效,因为它返回非const指针。此外,当您公开实现时,返回指向私有类数据的指针是不好的。

如果这是一个单例类,在线程开始之前在初始化时保存一些基本上静态的数据集,那么所有的访问器都应该是const方法。 getInstance还应该返回一个指向该类的const指针(并且是另一个答案所提到的静态方法)。