想象一个应用程序的功能,它需要多达5个线程来处理数据,这些线程使用缓冲区,互斥锁和事件来相互交互。性能至关重要,语言是C ++。
该功能可以作为一个(编译)单元实现,只有一个类,并且只能为该应用程序实例化该类的一个实例。该类本身实现了 run()方法中的一个线程,该方法生成其他4个线程,管理它们并在用户关闭应用程序时收集它们。
选择以下方法之一优于其他方法有什么好处(请告诉我更好的方法)?
以下是一些需要考虑的想法和问题(如果错误请纠正):
请假设没有像Qt这样的库来回答这个问题,然后假设我们可以依赖 QThread 并按 run()方法实现一个线程。
Edit1:每个设计固定线程数,数字5只是一个例子。请分享您对方法/模式的看法,而不是细节。
Edit2:我发现this answer(对于另一个问题)非常有帮助,我想第一种方法会误将类作为命名空间。如果与命名空间结合使用,则可以减轻第二种方法。
答案 0 :(得分:3)
首先,您应该阅读Herb Sutter的全部并发文章:
这是上一篇文章的链接,其中包含所有之前文章的链接。
根据以下文章:您有多少可扩展性?(http://drdobbs.com/parallel/201202924),您处于O(K): Fixed
案例中。也就是说,您有一组固定的任务要同时执行。
根据你的应用程序的描述,你有5个线程,每个线程做一个非常不同的事情,所以你必须有你的5个线程,也许希望其中一个或一些仍然可以将他们的任务分成多个线程(因此,使用线程池),但这将是一个奖励。
我让你阅读这篇文章了解更多信息。
忘记单身人士。这是dumb, overused pattern。
如果你真的想限制你班级的实例数量(严肃地说,你有什么比这更好的事情吗?),你应该把设计分成两部分:一个是数据类,一个是将前一个类包装成单例限制的类。
让您的标题和来源易于阅读。如果你需要将一个类的实现分成多个源,那么就这样吧。我相应地命名了来源。例如,对于MyClass类,我会:
最近的编译器能够内联来自不同编译单元的代码(我在Visual C ++ 2008,IIRC上看到了这个选项)。我不知道整个全局优化是否比“单元”编译更糟糕,但即使是这样,你仍然可以将代码划分为多个源,然后让一个全局源包含所有内容。例如:
然后相应地做你的包括。但是你应该确定这实际上会让你的表现变得更好:除非你真的需要它,否则不要进行优化。
您的问题和评论不是关于单片设计而是性能或线程问题,所以我可能错了,但您需要的是简单的重构。
我会使用第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 - 尽可能保护访问
如果一个类只使用一个函数,并且只使用一个类,并且实际上不需要访问类内部,那么它可以隐藏在匿名命名空间中。
如果一个变量仅由一个线程拥有,则将其作为私有变量成员隐藏在类中。
等