1

我在去过的几个工作场所都遇到过一个设计问题,但没有一个令人满意的解决方案:

假设您有一个具有动态线程数的系统。

每个线程必须可以访问一组“单例”,单例每个线程只有一个实例(因此它们不是真正的单例,而是每个线程的单例)

这组单例在编译时是已知的。

每个单例都有一个默认构造函数(为了简化事情,不过,没有这个约束的解决方案会很棒)

一个满意的解决方案应该有以下几点:

  1. 每个线程可以在 o(1) 时间内访问其任何单例

  2. 对单例的访问是无锁的

  3. 将单例添加到“单例集”不需要在集合端编写新代码

  4. 在编译期间填充“单例集”

我不确定这样的设计是否可行。如果是,我认为它需要一点元编程。

提前感谢您的任何见解。

4

4 回答 4

1

线程局部变量很好地解决了这个问题。

// in .h
class ThreadSingleton
{
private:
    static __thread ThreadSingleton* thread_specific_instance;

public:
    static ThreadSingleton* get() { return thread_specific_instance; }
    ThreadSingleton();
    ~ThreadSingleton();
};

// in .cc
__thread ThreadSingleton* ThreadSingleton::thread_specific_instance;

ThreadSingleton::ThreadSingleton() {
    if(thread_specific_instance)
        std::abort(); // one instance per thread please
    thread_specific_instance = this;
}

ThreadSingleton::~ThreadSingleton() {
    thread_specific_instance = 0;
}

// usage
int main() {
    // on thread entry
    ThreadSingleton x;

    // later anywhere in the thread
    ThreadSingleton* px = ThreadSingleton::get();
}

每个线程ThreadSingleton在堆栈上的某个位置创建,通常在线程函数中。稍后ThreadSingleton可以从该线程中的任何地方访问,通过ThreadSingleton::get()该线程返回调用线程的单例。(上面可以做一个模板来包装任何其他类,为了说明的简单我没有这样做)。

性能方面访问线程局部变量不需要任何调用(与使用创建的线程特定存储不同pthread_key_create)有关更多详细信息,请参阅http://www.akkadia.org/drepper/tls.pdf

于 2012-07-19T08:29:02.847 回答
1

除非我误解你,否则你描述的是线程本地存储。

在 C++11 中,您只需要声明一个变量thread_local即可为每个线程获取一个单独的实例。

在 C++03 中,最便携的解决方案是boost::thread_specific_ptr; 或者,您的编译器和/或系统库可能会提供特定于线程的存储,例如 POSIXpthread_key_create和朋友。

于 2012-07-19T08:54:35.070 回答
0

好的,通常我会在评论中发布这个,因为我不太确定我是否正确理解了你的问题......但是在父 Thread 类的构造函数中创建单例集的实例还不够?

假设您有三个类 A、B 和 C(在编译时已知)和一个“线程”类。

您不会在线程头中声明 A、B 和 C 的实例吗

class Thread {
private:
    A *a;
    B *b;
    C *c;
public:
    Thread();
}

然后在线程的构造函数中实例化它们?

Thread:Thread() {
    a = new A();
    b = new B();
    c = new C();
}

这样,每个线程独占“拥有”单例,这意味着它可以随时访问它们,而不必担心竞争条件或锁定。

关于“添加单例”,我是否可以建议创建一个“单例父”类,然后使用可以将新指针推送到的标准容器(如 std::list)?当然,对这个列表的访问必须用锁来保护,当你在运行时,在编译时这样做时,这不是必需的。在编译期间,您最好使用静态指针数组,这将允许您尽可能快地访问指针。

再次,对不起,如果我理解你的问题是错误的。

于 2012-07-19T08:12:59.987 回答
-1

我不确定我是否正确理解了你的问题,但对我来说,单例的“集合”是无关紧要的。您有固定数量的单例,将它们调用Singleton1到编译时已知的位置SingletonXX线程并不重要。

对于实际的单例,您可以让它们从处理每个线程部分的单个模板化基类继承。像这样的东西:

template<class B>
struct SingletonBase
{
    static B &getInstance()
        {
            // One instance per thread
            static std::unordered_map<std::thread::id, B> intances;

            if (instances.find(std::this_thread::get_id()) == instances.end())
                instances[std::this_thread::get_id()] = B{};

            return instances[std::this_thread::get_id()];
        }
};

class Singleton1 : public SingletonBase<Singleton1>
{
    // ...
};

如果您不希望为单例提供不同的不同类,则可以使用std::array它们来存储它们:

class Singleton : public SingletonBase<Singleton>
{
    // ...
};

std::array<Singleton, X> singletons;

这将在编译时创建一个指定数量的数组X,并且可以像普通数组一样访问Singleton &instance = singletons[0].getInstance();

请注意,我的示例代码使用“新”C++11 标准库中的功能。

于 2012-07-19T08:31:52.420 回答