线程静态类方法与全局范围

时间:2012-02-17 00:09:54

标签: c++ multithreading qt static-methods

想象一个应用程序的功能,它需要多达5个线程来处理数据,这些线程使用缓冲区,互斥锁和事件来相互交互。性能至关重要,语言是C ++。

该功能可以作为一个(编译)单元实现,只有一个类,并且只能为该应用程序实例化该类的一个实例。该类本身实现了 run()方法中的一个线程,该方法生成其他4个线程,管理它们并在用户关闭应用程序时收集它们。

选择以下方法之一优于其他方法有什么好处(请告诉我更好的方法)?

  1. 向类中添加5个静态方法,每个方法运行单个线程,互斥锁和其他作为静态类变量共享的数据。
  2. 添加5个全局函数(无范围)并使用全局变量,事件和互斥(就像它是C一样)
  3. 完全更改模式,再添加4个类,每个类实现一个线程并通过全局变量共享数据。
  4. 以下是一些需要考虑的想法和问题(如果错误请纠正):

    1. 让线程作为类成员(当然是静态的),他们可以依靠单例来访问非静态成员函数,它也为它们提供了一个名称空间,这本身就是一个好主意。
    2. 使用静态类方法,类头文件很快将包含许多静态变量(以及其他辅助静态方法)。必须在类头文件中声明变量可能会为包含头文件的其他单元带来额外的依赖性。如果全局声明的变量可以隐藏在单独的头文件中。
    3. 应该在代码中的某处定义静态类变量,因此它可以将输入声明的内容加倍。
    4. 编译器可以利用命名空间解析来获得更优化的代码(而不是可能在不同单元中的全局变量)。
    5. 单个单元可以更好地进行优化,而整个程序优化很慢,可能效果不佳。
    6. 如果单位增长,我必须将代码的某些部分移动到一个单独的单元,所以我将有一个具有多个(编译)单元的类,这是否是一个反模式?
    7. 如果使用多个类,每个处理一个线程,则可以再次提出相同的问题来确定静态方法和全局函数来实现线程。此外,这需要更多的代码留置权,而不是真正的问题,但它是否值得额外开销?
    8. 请假设没有像Qt这样的库来回答这个问题,然后假设我们可以依赖 QThread 并按 run()方法实现一个线程。

      Edit1:每个设计固定线程数,数字5只是一个例子。请分享您对方法/模式的看法,而不是细节。

      Edit2:我发现this answer(对于另一个问题)非常有帮助,我想第一种方法会误将类作为命名空间。如果与命名空间结合使用,则可以减轻第二种方法。

1 个答案:

答案 0 :(得分:3)

来源

首先,您应该阅读Herb Sutter的全部并发文章:

http://herbsutter.com/2010/09/24/effective-concurrency-know-when-to-use-an-active-object-instead-of-a-mutex/

这是上一篇文章的链接,其中包含所有之前文章的链接。

你的情况如何?

根据以下文章:您有多少可扩展性?http://drdobbs.com/parallel/201202924),您处于O(K): Fixed案例中。也就是说,您有一组固定的任务要同时执行。

根据你的应用程序的描述,你有5个线程,每个线程做一个非常不同的事情,所以你必须有你的5个线程,也许希望其中一个或一些仍然可以将他们的任务分成多个线程(因此,使用线程池),但这将是一个奖励。

我让你阅读这篇文章了解更多信息。

设计问题

关于单身人士

忘记单身人士。这是dumb, overused pattern

如果你真的想限制你班级的实例数量(严肃地说,你有什么比这更好的事情吗?),你应该把设计分成两部分:一个是数据类,一个是将前一个类包装成单例限制的类。

关于编译单元

让您的标题和来源易于阅读。如果你需要将一个类的实现分成多个源,那么就这样吧。我相应地命名了来源。例如,对于MyClass类,我会:

  • MyClass.hpp:标题
  • MyClass.cpp:主要来源(有构造函数等)
  • MyClass.Something.cpp:源代码处理
  • MyClass.SomethingElse.cpp:使用其他内容处理源代码

关于编译器优化

最近的编译器能够内联来自不同编译单元的代码(我在Visual C ++ 2008,IIRC上看到了这个选项)。我不知道整个全局优化是否比“单元”编译更糟糕,但即使是这样,你仍然可以将代码划分为多个源,然后让一个全局源包含所有内容。例如:

  • MyClassA.header.hpp
  • MyClassB.header.hpp
  • MyClassA.source.hpp
  • MyClassB.source.hpp
  • global.cpp

然后相应地做你的包括。但是你应该确定这实际上会让你的表现变得更好:除非你真的需要它,否则不要进行优化。

您的情况,但更好?

您的问题和评论不是关于单片设计而是性能或线程问题,所以我可能错了,但您需要的是简单的重构。

我会使用第3种方法(每个线程一个类),因为类具有私有/公共访问权限,因此,您可以使用它来保护一个线程拥有的数据,只需将其设置为私有。

以下指南可以为您提供帮助:

1 - 每个线程都应隐藏在一个非静态对象中

你可以使用该类的私有静态方法,也可以使用匿名命名空间的函数(我会使用该函数,但在这里,我想访问该类的私有函数,所以我会满足于静态方法)。

通常,线程构造函数允许您将指针传递给具有void *上下文参数的函数,因此使用它将this指针传递给主线程函数:

每个线程有一个类可以帮助你隔离该线程,从而隔离来自外部世界的线程数据:没有其他线程可以访问该数据,因为它是私有的。

以下是一些代码:

// Some fictious thread API
typedef void (*MainThreadFunction)(void * p_context) ;
ThreadHandle CreateSomeThread(MainThreadFunction p_function, void * p_context) ;

// class header
class MyClass
{
   public :
      MyClass() ;
      // etc.

      void         run() ;

   private :
      ThreadHandle m_handle ;

      static void  threadMainStatic(void * p_context) ;
      void         threadMain() ;
}

// source
void MyClass::run()
{
   this->m_handle = CreateSomeThread(&MyClass::threadMainStatic, this) ;
}

void MyClass::threadMainStatic(void * p_context)
{
   static_cast<MyClass *>(p_context)->threadMain() ;
}

void MyClass::threadMain()
{
   // Do the work
}

Displaimer:这未在编译器中测试过。将它作为伪C ++代码而不是实际代码。 YMMV。

2 - 识别未共享的数据。

此数据可以隐藏在拥有对象的私有部分中,如果它们受同步保护,则此保护过度(因为数据未共享)

3 - 确定共享的数据

...并验证其同步(锁定,原子访问)

4 - 每个班级都应有自己的标题和来源

...并在必要时使用同步保护对其(共享)数据的访问

5 - 尽可能保护访问

如果一个类只使用一个函数,并且只使用一个类,并且实际上不需要访问类内部,那么它可以隐藏在匿名命名空间中。

如果一个变量仅由一个线程拥有,则将其作为私有变量成员隐藏在类中。