快速路径数据包处理的配置更新

configuration update on a fast path packet processing

我们有一个使用线程池处理传入数据包的应用程序。每个线程都有一个在数据包处理时使用的配置。

我们目前正在使用互斥锁在检查配置是否更改之前进行锁定。

这使得线程花费太多时间来锁定互斥锁以检查是否有配置更新。我们想知道你们是否可以建议更快的替代方案。 实现是用 C++

此致。

解决此问题的一种可能方法是通过 atomics via std::atomic。以下是您问题的简化版本的简化解决方案。在下文中,您的问题已简化为单处理器线程(原则上多个情况相同)。第一个版本的解决方案 "leaks" 配置更改。对于足够少的配置更改(至少根据我的经验,这是一种非常常见的情况),这可能是可以接受的。否则,我将在最后描述两种解决方法。

假设您从以下配置开始 class:

#include <thread>
#include <vector>
#include <list>
#include <iostream>
#include <atomic>
#include <chrono>

constexpr int init_config_val = 3;

struct config{
    int m_val = init_config_val;
};

配置只有一个值字段,m_val

现在让我们为指向配置的原子指针和配置列表设置类型:

using config_atomic_ptr_t = std::atomic<config *>;
using config_list_t = std::list<config>;

线程进程采用指向原子配置指针的指针。当它需要访问配置时,它调用 std::atomic::load

void process(config_atomic_ptr_t *conf) {
    while(true) {
        const config *current_config = conf->load();
        ...
    }
}

(请注意,上面显示了线程在每次迭代时检查配置;在某些类型的应用程序中,检查它可能就足够了 "often enough"。)

当不同的线程想要设置配置时,它调用以下函数:

void modify_config(config_list_t &configs, config_atomic_ptr_t &current_config, config conf) {
    configs.push_back(conf);
    current_config.store(&*configs.rbegin());
}

该函数引用配置列表、引用原子配置指针和新配置对象。它将配置对象推送到列表的末尾,然后使用 std::atomic::store 将指针设置为列表中的末尾元素。

main 可以这样设置:

int main() {
    config_list_t configs;
    configs.push_back(config{});    
    config_atomic_ptr_t current_config{&*configs.rbegin()};

    std::thread processor(process, &current_config);
    config new_conf{init_config_val + 1};
    modify_config(configs, current_config, new_conf);
    processor.join();
}

如前所述,每次配置更改都会将一个新的配置对象推送到列表中,因此该程序实际上具有无限的内存需求。

至少根据我的经验,原则上许多应用程序需要支持配置更改,但预计这种情况很少见。如果是这样,上述解决方案可能是可以接受的。 (实际上,您可以通过删除列表来简化事情,只需在堆上分配新配置即可。)

如果没有,至少有两种选择。

第一个替代方案涉及修复上述问题,如下所示:

  • config 中,添加另一个描述配置版本的字段 - 例如,一个整数。
  • process 线程发送一个指向 std::atomic<int> 的指针。
  • 周期性地(比如每 1000 次迭代一次),线程将检查它正在使用的配置的版本,并设置 std::atomic<int> 来反映它。
  • 清理线程(可能是主线程)也会定期检查 std::atomic<int> 的值,并相应地清理列表。

第二种方法是将线程函数的指针传递给 boost::lockfree::queue。在每次迭代(或每迭代次数一次)中,线程可以检查队列中的新配置,然后使用它。


完整示例

#include <thread>
#include <vector>
#include <list>
#include <iostream>
#include <atomic>
#include <chrono>

constexpr int init_config_val = 3;

struct config{
    int m_val = init_config_val;
};

using config_atomic_ptr_t = std::atomic<config *>;
using config_list_t = std::list<config>;

void process(config_atomic_ptr_t *conf) {
    while(true) {
        const config *current_config = conf->load();
        if(current_config->m_val != init_config_val)
            break;
    }
}

void modify_config(config_list_t &configs, config_atomic_ptr_t &current_config, config conf) {
    configs.push_back(conf);
    current_config.store(&*configs.rbegin());
}

int main() {
    using namespace std::chrono_literals;

    config_list_t configs;
    configs.push_back(config{});    
    config_atomic_ptr_t current_config{&*configs.rbegin()};

    std::thread processor(process, &current_config);
    std::this_thread::sleep_for(1s);
    config new_conf{init_config_val + 1};
    modify_config(configs, current_config, new_conf);
    processor.join();
}