在多线程c++服务器应用程序中处理非常量全局配置

Handling non constant global configuration in a multithreaded c++ server application

本文关键字:非常 常量 全局 配置 处理 多线程 c++ 服务器 应用程序      更新时间:2023-10-16

我正在设计一个多线程c++服务器应用程序,该应用程序在自己的线程中为客户端提供服务,还拥有执行其他任务的各种其他工作线程。

所有这些线程都将依赖于一个文本配置文件,该文件需要能够在不退出并重新启动进程的情况下进行重新哈希。

我目前正在考虑让每个客户端和工作线程都有自己的配置对象副本,然后在每次rehash时进行更新。

我发现,将配置传递给所有其他我喜欢的实用程序函数,而我不认为这些函数应该是上述类的一部分,这似乎会变得非常乏味。

拥有一个全局配置会容易得多,除了需要大量的同步。

开放任何关于如何解决线程应用程序中具有非常量全局配置的想法!

我喜欢这样做的方法是始终将配置作为shared_ptr传递到const对象。每当你需要配置时(记住一致性;通常最好用相同的配置来处理整个请求,即使在请求处理代码结束时配置在技术上已经过时),你就会得到一个shared_ptr。后台工作任务可以在工作周期的方便点重置其shared_ptr的值,尽量避免保留配置太久(因为它可能会过时)。

如果只有一个任务可以更改配置,那么效果很好;它构造了一个全新的配置对象,然后重置它的sharedptr,[edit],它用锁来保护它,请参见下面的[/edit]。一旦没有其他任务使用旧的配置对象,旧的配置就会消失。

一个细节是:你不能传递指向配置对象部分的指针,除非你确定只要指针持续,你就会保留指向该配置对象的shared_ptr。例如,如果配置中包含子配置的名称映射,这可能会令人恼火。

虽然共享指针会有一些开销,但这可能比保持配置的整个副本同步要少(当然,除非配置很小,如果是的话,我们可能不会进行这种对话)。配置更改在大多数应用程序中相对罕见,因此在任何给定时间都不太可能有两个以上的配置对象。您通常可以安排将shared_ptr的创建保持为每个请求一个,因此shared_ptr同步是微不足道的。

YMMV,但我发现效果很好。

[编辑]正如几位评论者所指出的,我本应该明确锁定要求。配置更新程序保留一个受读写锁保护的主shared_ptr。在更新指针时,它需要保持一个写锁。它还导出一个接口,该接口将shared_ptr返回到当前配置;该接口在持有读锁的同时复制sharedptr。由于配置更改很少,而且shared_ptr很小,所以锁争用很少。[1]

除了配置任务本身,其他人都不应该担心锁,因为其他shared_ptr不应该由多个任务共享:每个任务都应该有自己的锁。

[1] 在写这篇文章的时候,我意识到,如果配置的析构函数很慢(例如,如果配置包含大量std::string),我一直在做的事情,包括在主shared_ptr上调用.reset,实际上可能会保持写锁太长时间。最好扩展reset的实现(这只是一个带有临时NULL shared_ptr的交换),将交换放在锁保护内部,并让(临时的)析构函数解锁运行。然而,考虑到配置更改是多么罕见(至少在我关联过的任何服务器中),我怀疑它是否有任何明显的不同。

我不明白这部分:

我发现的一件事是,将配置传递给我更喜欢但不认为应该是上述类的一部分的所有其他实用程序函数似乎会变得非常乏味

无论如何,请查找线程本地存储。您可以创建一个线程本地的singleton配置对象。

这可以用一个由所有线程共享的单个配置对象来处理。这个配置类应该有两个部分:

  1. 读取和解析配置文件
  2. 所有线程对配置数据的访问

配置文件的读取和解析应该只从一个线程处理,最好是从主线程处理。此部分将与其他线程隔离。当更新完成后,其他线程访问的配置数据可以通过pthread_rwlock使用写锁进行更新。当线程访问配置数据时,它们应该使用读锁。

如果您不熟悉rw(读/写)锁,可能会有多个同时的读锁,除非执行写锁,否则不会阻塞。一次只能有一个写锁。因此,在这种情况下,所有线程都可以在没有任何锁定争用的情况下同时读取配置数据。只有当主线程在rehash之后更新配置数据时,才会在读取时锁定。

根据定义,如果线程正在访问和更改对象,那么在多线程应用程序中拥有可变全局对象将需要同步。您可以保留全局配置,但需要注意的是,如果在访问配置时添加额外的层,仍然不必担心同步问题。每个线程都可以有一个指向全局配置的指针,如果每个线程都只是从配置中读取,那么这是可以的。但是,当您需要更改配置时,请为正在访问的成员设置一个标志,并将其存储在线程的本地位置。每次访问配置时,都必须检查此标志,然后返回相应的数据。这在代码设计中有很大的优势,而且它迫使你充实到配置的接口,所以如果你将来决定改变访问配置的方式,那么所有使用配置的代码都不必更改。

除了已经发布的解决方案之外,另一个好的解决方案是DB表。DB可以通过配置更好地控制事务,从而提供更好的数据完整性。它可以提供保护,防止对现有配置进行不适当的更改,因为更改是基于旧的、过时的配置。

是的,对于OP的使用来说,它更慢,而且可能会过度使用(尤其是在没有DB的情况下!),但为了完整性,应该提到它。