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

发布于 2025-01-06 09:26:49 字数 1189 浏览 0 评论 0原文

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

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

选择以下方法之一相对于另一种方法有什么优势(请让我知道任何更好的方法)?

  1. 向类添加 5 个静态方法,每个方法运行一个线程,互斥体和其他数据作为静态类变量共享。
  2. 添加 5 个全局函数(无作用域)并使用全局变量、事件和互斥体(就好像它是 C)
  3. 完全改变模式,添加 4 个类,每个类实现一个线程并通过全局变量共享数据。

这里有一些需要考虑的想法和问题(如果错误请纠正):

  1. 将线程作为类成员(当然是静态的),它们可以依赖单例来访问非静态成员函数,它也给了它们一个命名空间这本身似乎是个好主意。
  2. 使用静态类方法,类头文件很快将包含许多静态变量(以及其他辅助静态方法)。必须在类头文件中声明变量可能会给包含该头文件的其他单元带来额外的依赖性。如果全局声明变量,则它们可以隐藏在单独的头文件中。
  3. 静态类变量应该在代码中的某个位置定义,因此它使类型声明的内容加倍。
  4. 编译器可以利用命名空间解析来获得更优化的代码(而不是可能位于不同单元中的全局变量)。
  5. 单个单元有可能得到更好的优化,而整个程序的优化速度很慢,而且可能效果较差。
  6. 如果单元增长,我必须将代码的某些部分移动到单独的单元,所以我将拥有一个具有多个(编译)单元的类,这是否是反模式?
  7. 如果使用多个类,每个类处理一个线程,那么可以再次提出同样的问题来决定静态方法和全局函数之间来实现线程。此外,这需要更多的代码留置权,这不是一个真正的问题,但值得额外的开销吗?

请假设没有 Qt 等库,然后假设我们可以依赖 QThread 并为每个 run() 方法实现一个线程,然后回答这个问题。

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

Edit2:我发现这个答案(针对另一个问题)非常有帮助,我想第一种方法滥用类作为名称空间。如果与命名空间结合使用,可以减轻第二种方法的影响。

Imagine a functionality of an application that requires up to 5 threads crunching data, these threads use buffers, mutex and events to interact with each other. The performance is critical, and the language is C++.

The functionality can be implemented as one (compilation) unit with one class, and only one instance of this class can be instantiated for the application. The class itself implements 1 of the threads in run() method, which spawns other 4 threads, manages them and gathers them when user closes the application.

What is the advantage of choosing one of the following method over another (please do let me know of any better approach)?

  1. Add 5 static methods to the class, each running a single thread, mutex and other data shared as static class variables.
  2. Add 5 global functions (no scope) and use global variables, events and mutex (as if it is C)
  3. change the pattern entirely, add 4 more classes each implementing one of the threads and share data via global variables.

Here are some thoughts and issues to be considered (please correct them if they are wrong):

  1. Having threads as class members (static of course), they can rely on the singleton to access non-static member functions, it also gives them a namespace which by itself seems a good idea.
  2. Using static class methods, the class header file soon will contain many static variables (and other helper static methods). Having to declare variables in the class header file may bring additional dependencies to other units that include the header file. If variables where declared globally they could be hidden in a separate header file.
  3. Static class variables should be defined somewhere in the code, so it doubles typing declaration stuff.
  4. Compilers can take advantage of the namespace resolution for more optimized code (as opposed to global variables possibly in different units).
  5. The single unit can potentially be better optimized, whereas whole program optimization is slow and probably less fruitful.
  6. If the unit grows I have to move some part of the code to a separate unit, so I will have one class with multiple (compilation) units, is this a anti-pattern or not?
  7. If using more than one class, each handling one thread, again same question can be made to decide between static methods and global functions to implement the threads. In addition, this requires more lien of code, not a real issue but does it worth the additional overhead?

Please answer this assuming no library such as Qt, and then assuming that we can rely on QThread and implement one thread per run() method.

Edit1: The number of threads is fixed per design, number 5 is just an example. Please share your thoughts on the approaches/patterns and not on details.

Edit2: I have found this answer (to a different question) very helpful, I guess the first approach misuses classes as namespaces. Second approach can be mitigated if coupled with namespace.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

爱,才寂寞 2025-01-13 09:26:49

首先

,您应该阅读 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 个线程,也许希望其中一个或一些线程仍然可以将它们的任务划分为多个线程(因此,使用一个线程池),但这将是一个额外的好处。

我让您阅读这篇文章以获取更多信息。

设计问题

关于单例

忘记单例吧。这是一个愚蠢且过度使用的模式

如果您真的想限制类的实例数量(说真的,您没有比这更好的事情了吗?),您应该将设计分为两部分:一个用于数据的类,一个用于包装的类前面的类进入了单例限制。

关于编译单元

让您的标题和源代码易于阅读。如果您需要将一个类的实现放到多个源中,那就这样吧。我相应地命名了来源。例如,对于类 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

,然后相应地进行包含。但您应该确保这实际上可以提高您的性能:除非您确实需要它并且您对其进行了分析,否则不要进行优化。

你的情况,但更好?

您的问题和评论更多地涉及整体设计,而不是性能或线程问题,所以我可能是错的,但您需要的是简单的重构。

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

以下指南可以帮助您:

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 - 尽可能保护访问

如果一个函数由一个类使用,并且仅一个类使用,并且并不真正需要访问类内部,那么它可以隐藏在匿名名称空间中。

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

ETC。

Sources

First, you should read the whole concurrency articles from Herb Sutter:

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

This is the link to the last article's post, which contains the links to all the previous articles.

What's your case?

According to the following article: How Much Scalability Do You Have or Need? ( http://drdobbs.com/parallel/201202924 ), you are in the O(K): Fixed case. That is, you have a fixed set of tasks to be executed concurrently.

By the description of your app, you have 5 threads, each one doing a very different thing, so you must have your 5 threads, perhaps hoping one or some among those can still divide their tasks into multiple threads (and thus, using a thread pool), but this would be a bonus.

I let you read the article for more informations.

Design questions

About the singleton

Forget the singleton. This is a dumb, overused pattern.

If you really really want to limit the number of instances of your class (and seriously, haven't you something better to do than that?), You should separate the design in two: One class for the data, and one class to wrap the previous class into the singleton limitation.

About compilation units

Make your headers and sources easy to read. If you need to have the implementation of a class into multiple sources, then so be it. I name the source accordingly. For example, for a class MyClass, I would have:

  • MyClass.hpp : the header
  • MyClass.cpp : the main source (with constructors, etc.)
  • MyClass.Something.cpp : source handling with something
  • MyClass.SomethingElse.cpp : source handling with something else
  • etc.

About compiler optimisations

Recent compiler are able to inline code from different compilation units (I saw that option on Visual C++ 2008, IIRC). I don't know if whole global optimization works worse than "one unit" compilation, but even if it is, you can still divide your code into multiple sources, and then have one global source include everything. For example:

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

and then do your includes accordingly. But you should be sure this actually makes your performance better: Don't optimize unless you really need it and you profiled for it.

Your case, but better?

Your question and comments speak about monolithic design more than performance or threading issue, so I could be wrong, but what you need is simple refactoring.

I would use the 3rd method (one class per thread), because with classes comes private/public access, and thus, you can use that to protect the data owned by one thread only by making it private.

The following guidelines could help you:

1 - Each thread should be hidden in one non-static object

You can either use a private static method of that class, or an anonymously namespaced function for that (I would go for the function, but here, I want to access a private function of the class, so I will settle for the static method).

Usually, thread construction functions let you pass a pointer to a function with a void * context parameter, so use that to pass your this pointer to the main thread function:

Having one class per thread helps you isolate that thread, and thus, that thread's data from the outer world: No other thread will be able to access that data as it is private.

Here's some code:

// 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: This wasn't tested in a compiler. Take it as pseudo C++ code more than actual code. YMMV.

2 - Identify the data that is not shared.

This data can be hidden in the private section of the owning object, and if they are protected by synchronization, then this protection is overkill (as the data is NOT shared)

3 - Identify the data that is shared

... and verify its sychronization (locks, atomic access)

4 - Each class should have its own header and source

... and protect the access to its (shared) data with synchronization, if necessary

5 - Protect the access as much as possible

If one function is used by a class, and only a class, and does not really need access to the class internals, then it could be hidden in an anonymous namespace.

If one variable is owned by only a thread, hide it in the class as a private variable member.

etc.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文