从boost::lockfree::spsc_队列筛选项目

从boost::lockfree::spsc_队列筛选项目,boost,queue,Boost,Queue,我使用boost::lockfree::spsc_队列将流数据从线程发送到工作线程 以下是项目的结构: struct spsc_queue_item { uint8_t ID; void *data; }; 数据由spsc_queue.push插入,由另一个线程通过spsc_queue.pop读取 但我也有一些工作线程的“命令”。 就像ID 0是“启动筛选器”, ID 1是“停止过滤器”, ID 2 ID“数据” 因此,如果大量“数据”被推送到队列中,“stop filter”

我使用boost::lockfree::spsc_队列将流数据从线程发送到工作线程

以下是项目的结构:

struct spsc_queue_item
{
    uint8_t ID;
    void *data;
};
数据由spsc_queue.push插入,由另一个线程通过spsc_queue.pop读取

但我也有一些工作线程的“命令”。 就像ID 0是“启动筛选器”, ID 1是“停止过滤器”, ID 2 ID“数据”

因此,如果大量“数据”被推送到队列中,“stop filter”之类的命令将被延迟,因为首先要处理“数据”项。 但是,如果命令“停止过滤器”在“数据”项目是无用的,可以丢弃

现在我知道还有成员函数“consume_one”和“consume_all”

但是我没有找到一个例子来说明如何在这些函数中使用函子。 我的想法是使用consume_one作为示例,在继续处理ID==2的数据项之前,首先检查ID==0或ID==1的项是否在队列中

有人举了一个小例子,如何使用函子过滤出与请求的ID相关的项

或者是否有其他通过“优先级”标志从队列中获取项目的快速方法

更新针对sehe的回答,提供更多信息:


谢谢你提供的信息

有没有办法让它变得更好

我需要向工作线程发送信号,如“启动筛选器”、“停止筛选器”

我在考虑使用事件:

SetEvent(hStartFilter);
但在这里,我必须为每个命令使用、创建和关闭单个事件

“数据”也可以有不同的ID。 与工作线程一样,工作线程接收:

"start filter" with ID=0
"start filter" with ID=1
然后ID0和ID1的“数据”进入队列。 现在线程接收ID0的“停止过滤器”。 因此,ID0数据队列中的所有项目都已过时,可以删除

我的第一个测试是绘制队列中的所有项目。检查每一个匹配的ID并移除该项目。剩下的其他项目将在之后被推回到队列中。 但是,如果有大量具有不同ID(最多32个)的数据,那么这将占用大量CPU使用和耗时。队列的最大大小为2048个项目


还有更好的方法吗?

队列具有FIFO语义。无法进行筛选,除非您只想丢弃不符合条件的元素。(只需包装
pop
功能)

典型的解决方案是使用

  • 优先级队列
  • 两个单独的队列,一个用于数据,一个用于命令

如果在数据队列中不能超过X元素,请考虑使用循环缓冲区。code>spsc_队列使用环形缓冲区作为底层存储

更新为了响应,我决定使用每个ID的带外过滤状态信号创建一个演示

让我先介绍一下常见的定义:

static constexpr uint8_t NUM_SOURCES = 32;
现在,消费者和生产者都有共同的定义:

namespace queueing {
    using data_t = std::vector<char>; // just for demo

    struct spsc_queue_item {
        uint8_t ID;
        data_t  data;
    };

    // process control
    boost::atomic_bool shutdown_flag { false };

    namespace statistics {
        namespace events {
            boost::atomic_size_t occurred  { 0 };
        }

        namespace packets {
            boost::atomic_size_t queued    { 0 };
            boost::atomic_size_t dropped   { 0 };
            boost::atomic_size_t processed { 0 };
            boost::atomic_size_t skipped[NUM_SOURCES] = {};
        }

        boost::atomic_size_t idle_cycles   { 0 };

        void report();
    }

    // business logic
    boost::atomic_bool   source_enabled  [NUM_SOURCES] = {}; // true:started (process) / false:stopped (skip)
    boost::lockfree::spsc_queue<spsc_queue_item, boost::lockfree::capacity<2048> > shared_queue;
}
线程函数的主体非常简单,但值得注意的是,
start
stop
事件不会通过队列进行通信

制作人更简单。它所做的只是耗尽队列,更新一些统计计数器。 在处理项目之前,检查相应的筛选状态(
source\u enabled
):

void consumer_thread() {
    namespace stats = queueing::statistics;

    queueing::spsc_queue_item item;

    auto consume_pending = [&] {
        while (queueing::shared_queue.pop(item)) {
            if (queueing::source_enabled[item.ID])
                fake_process(item); // if filtering started, process 
            else
                stats::packets::skipped[item.ID]++; // if filtering stopped, skip 
        }
    };

    while (!queueing::shutdown_flag) {
        consume_pending();
        stats::idle_cycles++;
    }

    consume_pending(); // drain any remaining queued items, to avoid race with shutdown_flag
}
现在,一切都应该是不言自明的,因此,拼凑一个
main()
函数:

int main() {
    using namespace std;

    // check no source_enabled flags are set at start
    assert(0 == count(begin(queueing::source_enabled), end(queueing::source_enabled), true));

    auto producer = thread(producer_thread);
    auto consumer = thread(consumer_thread);

    this_thread::sleep_for(chrono::seconds(1));
    queueing::shutdown_flag = true;

    if (producer.joinable()) producer.join();
    if (consumer.joinable()) consumer.join();

    queueing::statistics::report();
}
我们的程序运行两个线程大约1秒,并等待它们加入。 然后它会报告统计数据,在我的系统上,这些统计数据如下所示:

++-+++++++--+++-++++-++-+++---+-+-+-+++++-+--+---+++-++---+-++-++-+-+++---++--+++-++---+----+-+-+-+--+++-++--+--+--++--+-+-+-+--+--+++--++-+-++-++-+--+--+++-++-+---+----++-+++-+-++-+----+--+-+-+--+++--+++++-+-+--++-+--++++-+-+---++-+---+-+--++---++++----+-+---+-+-+-+--+-++--+-+++--+++-+----+-+-+-+++-+++--+-++-++++++---++--+-++-++---+-+-++--+-+-----++---+-+-+--+++--++---++--+-+++-++++-+++-+-+--+++-+-+----+-++++-+--+++----+++-------+-++-+-+-++++-++++---++-+---+-++-----+-++++----+++-++++--+--+-----+-++++----++++-+++-+---+---+-+-++++-++---+-++-+-+-+++-+-+--+-----++-+++---+-++---+++-++-+--+++++------++---+-++++-+-+-+--++++-++++-+--+++-++---+-----++-+-++-+-+++--++-+-+-++-++-----+-++--+--+--+-------++--+-++-+--++-++-++--+-+-++-+-+++-++++-+---+--+++--++--+-+++++-+-----++--++--+++--++-+---++----+--+-+--++-++---+++++++-+--+-++---+----+-+-+--+-+-+--++++-++--+--+-+---+++-+++++++-++-+-----+--++------+-++++++--++-++-+---+-++---++-++------+-++--+-++-+++--+++-+++-+-+--+-+--+--+---+-+-+-+--+-++-+-++---+++-+-+-++--+-++-+---++--+-+--++-+++-+--+++---+----+--++-++++++-++-+----+++-+-+--+++-----+---+--++-+--+-++++++-+-+++--+++---+-+-++++-++-+-+----++++----+++-++----+---++-+---++-+-+-++--+++---+--+++----++-++-+++--+--+---+++--+--+--+--+--++++-++++---+-+-+--+-+-+--++++--+-+--++--++++----++-++++++-+--+-+------+-----+++----++-+++++-+--+--+---++-+-++-+--++++-+++---+++-+----+--+++++-+-+--+++--++-+++-+-++---++-++-+-+-+--+-++--+---+-+++--+++++-----+-++-+-+++-+-+-------++++---+-+-++-+--+++++---+--++-+-++-+++----+++-++++---++------+-+---++++--+-+---+++------++++++---++-+----+-+++-+--++-+-+-+-----+-++-++-++--++-+-+-++++++--++---+-+-+-+-+-+-++-++-++----++--++-+++-++---+++--+++---+++--+-+++----++--+-+-+++---++---++-+--+++++-+---++----++--+++-+--+-+++++++-+--+---+--+---+----+-++-++-+--++--+--++-++---+++++--+-+---+-++-+-+----+++-++-+-+--+---+-++-+-----++---++++--+++++-+---+-++--+-+-+----+--++++-+-----++++--++-+-+++++----+++---+++++++--+---+--+--++++--+++-----+-++--+-+-----+++++----+-++++---+-++--+-++-+++--+++-+-+++++--+----++--+--+-+-++-----++-+--++--++++++-+-+++----++++---++-+--+-+------+-+--+++++--+++--++-----+--++-++-+++++-++-------+----++-++--+--++--++++-++---+-+++++----+-++-++---+++---+-++-++----++--++--+++++-+--+-----+-+-+-+-+++-+--++-+-+++--+-+-+++-+-++--+-+-+-+--+-+-+++++---+---+-++-+---++-+-++-+-+++-++-++-+-++-------++---+-++-++++-++--++--+-++-+++---++++--+----+---+-++-+++--+-+++---+-++-++----+--+--+-++--+-++-++++++--+-++-+--+---+-+--+-+--++---+--+-++--++--+--++-++++----+--+--+++-+++-+-+-++--++-+-+---+-+-------+--++++++-++++++-++-+-++-+---+--+-+-++--+++---+----+--+--+-++----+-+-++-++-++-+++--++---++-------+++++--+-+++++++--+--+-+--++--++--++-+--+--+++----+++++-++-------++---+-+--++-++--+++-+-+-+-+------+-+--+++++-+-+--++-++-++--+++++++---+-++--+++-+++--++++-++--+-+---+----+----+---+--+-+++-+-+++++---+--++--+-+++-+++++--+---+-+++++-+---++++--+-++----+---++----+++---+++++-+-++--+--+-++-++----+---++-++-+-+-+---+++-++-+++-+---+++--+-+-----++-+---++-+---++---+-++--++++-+--++-+-++----+-+-+--++--++++--+--++--+--+-+-+++++++--++-+-+-+++--+---+++--++++++--+-+-----+---++-+++--+++--++---+++--+--+-++++-----+++-----++++--++--+-+--
Events occurred:   3061
Queued packets:    3061
Dropped packets:   0
Processed packets: 1464
Filtered (per source) 58 48 53 51 47 39 45 42 53 52 57 50 63 43 49 57 45 58 40 42 56 54 58 52 44 53 61 41 50 33 51 52 
Total filtered:   1597
Idle cycles:      26408166
第一行(
++-+++++-+++++-+++-++-++-++-+
..)是一个简写符号,显示
源代码启用[]
标志中的有效更改数

您可以看到,在这个速率下,队列没有饱和,使用者线程有相当多的空闲周期

演示 完整列表供参考:

#include <boost/lockfree/spsc_queue.hpp>
#include <boost/atomic.hpp>
#include <boost/random.hpp>
#include <boost/bind.hpp>
#include <thread>
static constexpr uint8_t NUM_SOURCES = 32;

namespace queueing {
    using data_t = std::vector<char>; // just for demo

    struct spsc_queue_item {
        uint8_t ID;
        data_t  data;
    };

    // process control
    boost::atomic_bool shutdown_flag { false };

    namespace statistics {
        namespace events {
            boost::atomic_size_t occurred  { 0 };
        }

        namespace packets {
            boost::atomic_size_t queued    { 0 };
            boost::atomic_size_t dropped   { 0 };
            boost::atomic_size_t processed { 0 };
            boost::atomic_size_t skipped[NUM_SOURCES] = {};
        }

        boost::atomic_size_t idle_cycles   { 0 };

        void report() {
            namespace stats = queueing::statistics;
            std::cout << "\n";
            std::cout << "Events occurred:   " << stats::events::occurred   << "\n";
            std::cout << "Queued packets:    " << stats::packets::queued    << "\n";
            std::cout << "Dropped packets:   " << stats::packets::dropped   << "\n";
            std::cout << "Processed packets: " << stats::packets::processed << "\n";

            std::cout << "Filtered (per source) ";
            std::copy(std::begin(stats::packets::skipped), std::end(stats::packets::skipped), 
                    std::ostream_iterator<size_t>(std::cout, " "));
            std::cout << "\n";

            auto total_filtered = std::accumulate(std::begin(stats::packets::skipped), std::end(stats::packets::skipped), 0ul);
            std::cout << "Total filtered:   " << total_filtered << "\n";
            std::cout << "Idle cycles:      " << stats::idle_cycles      << "\n";
        }
    }

    // business logic
    boost::atomic_bool   source_enabled  [NUM_SOURCES] = {}; // true:started (process) / false:stopped (skip)
    boost::lockfree::spsc_queue<spsc_queue_item, boost::lockfree::capacity<2048> > shared_queue;
}

void producer_thread() {
    using namespace boost;
    namespace stats = queueing::statistics;
    // generate random data packets or start/stop filter events

    using queueing::spsc_queue_item;

    mt19937 engine;
    auto gen_srce = bind(uniform_int<uint8_t>(0, NUM_SOURCES-1), ref(engine));
    auto gen_data = [&] {
        std::vector<char> v; 
        std::generate_n(back_inserter(v), engine()%1024, bind(uniform_int<uint8_t>{}, ref(engine)));
        return v;
    };
    enum kind_t { knd_data, knd_start, knd_stop };
    auto gen_kind = bind(uniform_int<uint8_t>(knd_data, knd_stop), ref(engine));

    queueing::data_t const empty {};

    // 
    struct event_t { kind_t kind; spsc_queue_item item; };
    auto gen_event = [&] {
        auto kind = static_cast<kind_t>(gen_kind());
        return event_t {
            kind,
            spsc_queue_item {
                gen_srce(),
                kind == knd_data? gen_data() : empty
            }
        };
    };

    // now that we can easily generate queue items, let's do so in a loop
    while (!queueing::shutdown_flag) {
        auto evt = gen_event();

        std::this_thread::sleep_for(std::chrono::nanoseconds(engine()%102400));

        switch(evt.kind) {
            case knd_data:
                stats::events::occurred++;

                if (queueing::shared_queue.push(evt.item)) {
                    stats::packets::queued++;
                } else {
                    stats::packets::dropped++;
                }
                break;
            case knd_start:
                {
                    bool expected = false;
                    if (queueing::source_enabled[evt.item.ID].compare_exchange_weak(expected, true))
                        std::cout << "+";// << static_cast<int>(evt.item.ID);
                }
                break;
            case knd_stop:
                {
                    bool expected = true;
                    if (queueing::source_enabled[evt.item.ID].compare_exchange_weak(expected, false))
                        std::cout << "-";// << static_cast<int>(evt.item.ID);
                }
                break;
        }
    }
}

void fake_process(queueing::spsc_queue_item const& item) {
    // pretend it takes time proportional to the amount of data
    std::this_thread::sleep_for(std::chrono::microseconds(item.data.size()));

    queueing::statistics::packets::processed++;
}

void consumer_thread() {
    namespace stats = queueing::statistics;

    queueing::spsc_queue_item item;

    auto consume_pending = [&] {
        while (queueing::shared_queue.pop(item)) {
            if (queueing::source_enabled[item.ID])
                fake_process(item); // if filtering started, process 
            else
                stats::packets::skipped[item.ID]++; // if filtering stopped, skip 
        }
    };

    while (!queueing::shutdown_flag) {
        consume_pending();
        stats::idle_cycles++;
    }

    consume_pending(); // drain any remaining queued items, to avoid race with shutdown_flag
}

#include <cassert>

int main() {
    using namespace std;

    // check no source_enabled flags are set at start
    assert(0 == count(begin(queueing::source_enabled), end(queueing::source_enabled), true));

    auto producer = thread(producer_thread);
    auto consumer = thread(consumer_thread);

    this_thread::sleep_for(chrono::seconds(1));
    queueing::shutdown_flag = true;

    if (producer.joinable()) producer.join();
    if (consumer.joinable()) consumer.join();

    queueing::statistics::report();
}
#包括
#包括
#包括
#包括
#包括
静态constexpr uint8_t NUM_SOURCES=32;
命名空间队列{
使用data\u t=std::vector;//仅用于演示
结构spsc_队列_项{
uint8_t ID;
数据;
};
//过程控制
boost::原子布尔关闭标志{false};
命名空间统计信息{
命名空间事件{
boost::原子大小\u t发生{0};
}
命名空间数据包{
原子大小排队{0};
boost::原子大小为{0};
boost::原子大小处理{0};
boost::原子大小跳过[NUM\u SOURCES]={};
}
boost::原子大小\u t空闲\u循环{0};
作废报告(){
namespace stats=queueing::statistics;

std::cout队列具有FIFO语义。无法进行筛选,除非您只想丢弃不符合条件的元素。(只需包装
pop
函数)

典型的解决方案是使用

  • 优先级队列
  • 两个单独的队列,一个用于数据,一个用于命令

如果在数据队列中不能超过X元素,请考虑使用循环缓冲区。<代码> SPSCHealQue2/<代码>使用环形缓冲区作为基础存储。

更新为了响应,我决定使用每个ID的带外过滤状态信号创建一个演示

让我先介绍一下常见的定义:

static constexpr uint8_t NUM_SOURCES = 32;
现在,消费者和生产者都有共同的定义:

namespace queueing {
    using data_t = std::vector<char>; // just for demo

    struct spsc_queue_item {
        uint8_t ID;
        data_t  data;
    };

    // process control
    boost::atomic_bool shutdown_flag { false };

    namespace statistics {
        namespace events {
            boost::atomic_size_t occurred  { 0 };
        }

        namespace packets {
            boost::atomic_size_t queued    { 0 };
            boost::atomic_size_t dropped   { 0 };
            boost::atomic_size_t processed { 0 };
            boost::atomic_size_t skipped[NUM_SOURCES] = {};
        }

        boost::atomic_size_t idle_cycles   { 0 };

        void report();
    }

    // business logic
    boost::atomic_bool   source_enabled  [NUM_SOURCES] = {}; // true:started (process) / false:stopped (skip)
    boost::lockfree::spsc_queue<spsc_queue_item, boost::lockfree::capacity<2048> > shared_queue;
}
线程函数的主体非常简单,但值得注意的是,
start
stop
事件不会通过队列进行通信

生成器更简单,它所做的只是耗尽队列,更新一些统计计数器。 在处理项目之前,检查相应的筛选状态(
source\u enabled
):

void consumer_thread() {
    namespace stats = queueing::statistics;

    queueing::spsc_queue_item item;

    auto consume_pending = [&] {
        while (queueing::shared_queue.pop(item)) {
            if (queueing::source_enabled[item.ID])
                fake_process(item); // if filtering started, process 
            else
                stats::packets::skipped[item.ID]++; // if filtering stopped, skip 
        }
    };

    while (!queueing::shutdown_flag) {
        consume_pending();
        stats::idle_cycles++;
    }

    consume_pending(); // drain any remaining queued items, to avoid race with shutdown_flag
}
现在,一切都应该不言自明,所以,