正在等待线程还是创建新线程?

时间:2019-10-01 16:01:48

标签: c++ multithreading mutex

我要决定我在嵌入式平台上运行的东西的编码方式,并希望在这种情况下可以使用一般的“经验法则”。编码我的想法然后进行基准测试显然是最好的方法,但是在我的特定情况下,要想从该平台中获取任何有意义的甚至准确的结果将非常棘手。我还确定可能会有其他人在各自的平台上有相同的问题,所以我决定在这里提问。请客气,因为我对线程库不是很熟悉,因此建设性的反馈将非常有用。

我有许多线程(最多大约10-20个线程)都想写入此硬件设备。因此,我决定使用一个简单的环形缓冲区,该缓冲区由两个8k的缓冲区(主要/辅助)组成。这样,可以及时处理每个传入线程。到达的线程将获取一个互斥锁并写入主缓冲区,然后释放其互斥锁以准备下一个线程。现在,当主缓冲区已满时,新的传入线程显然会切换为使用辅助缓冲区,然后开始将主缓冲区写入硬件设备。

所以问题实际上是...如何最好地写入硬件设备???我在想有两种选择:

  1. 一旦缓冲区已满,就创建一个执行写操作的新线程。

  2. 向预先创建的等待工作线程发出信号以执行写操作。

这两个选项似乎都有各自的优点/缺点。选项1是最简单的编码,可以通过多种方法进行,但是其有效性取决于创建/启动线程的成本。线程将被创建,它将执行写操作,然后将死亡。选项2似乎是性能最高的,但是如果您要拥有一个可重用的线程,则需要一个互斥锁和几个条件变量来控制它。一个通知线程数据已经准备好,另一个请求线程在程序结束时终止。加上一些原子,可以进行虚假的唤醒/丢失通知等,您将获得相当复杂的解决方案。

那么什么是最好的方法?通常来说,线程是繁重的创建/启动任务,还是完全依赖于平台的事物,而基准测试是唯一的了解方法?使用一种我从未想过的方法有什么好处?

-这是针对没有TL; DR综合征的人的-

我确定你们中的某些人已经想知道如果辅助缓冲区在写操作完成之前变满会发生什么情况?就我而言,答案很简单:这永远都不会发生!尽管写操作很慢,但它永远不会足够慢,以至于在写操作完成之前已填充了辅助缓冲区。但是,如果有人要使用此环形缓冲区方法,则必须为这种情况做好准备。我考虑的解决方法是在写操作期间保留另一个互斥体。这意味着原本应写入缓冲区的线程将阻塞,直到完成写入并释放互斥锁为止。

这是我在选择选项2之后大致得出的结论,但看起来非常混乱。我实际上想使用promise / futures来避免条件变量上的自旋锁谓词,但是想不出一种将promise移到已经创建的线程中的好方法。无论如何...非常感谢反馈,反馈错误,嗯,我对线程库不是太熟悉。

class Bar
{
    public:
        Bar(const size_t size) : buffer(new uint8_t[size]), buffer_size(size), used_size(0) {}
        const size_t GetRemainingBufferSize(void) const { return buffer_size - used_size; }
        const size_t GetUsedBufferSize(void) const { return used_size; }
        const uint8_t* GetBuffer(void) const { return buffer.get(); }
        const size_t GetBufferSize() const { return buffer_size; }
        void ResetBuffer(void) { used_size = 0; }
        void WriteIntoBuffer(const vector<uint8_t>& data)
        {
            std::copy(data.begin(), data.end(), buffer.get() + used_size);
            used_size += data.size();
        }
        private:
            std::unique_ptr<uint8_t[]> buffer;
        size_t buffer_size;
        size_t used_size;
};

class Foo
{
    public:
        Foo(const size_t buffer_size = 8192) : bar_buffers{ buffer_size, buffer_size }, primary_buffer(&bar_buffers[0]), secondary_buffer(&bar_buffers[1]),
            write_predicate(false), quit_predicate(false), write_buffer(primary_buffer)
        {
            foo_thread = std::thread(&Foo::WriteHWThread, this);
        }
        ~Foo()
        {
        quit_predicate = true;
        begin_write.notify_one();
        if (foo_thread.joinable())
            foo_thread.join();
        }
        Foo(const Foo&) = delete;
        Foo& operator=(const Foo&) = delete;
        void WriteData(const std::vector<uint8_t>& data)
        {
            if (std::lock_guard<std::mutex> foo_lk(foo_lock); primary_buffer->GetRemainingBufferSize() < data.size())
            {
                std::unique_lock<std::mutex> write_lk(write_lock);
                write_buffer = primary_buffer;
                write_lk.unlock();
                std::swap(primary_buffer, secondary_buffer);
                primary_buffer->ResetBuffer();
                write_predicate = true;
                begin_write.notify_one();
            }
            primary_buffer->WriteIntoBuffer(data);
        }
        void WriteHWThread(void)
        {
            do
            {
                std::unique_lock<std::mutex> write_lk(write_lock);
                begin_write.wait(write_lk, [&]() -> bool { return write_predicate.load() || quit_predicate.load(); });
                write_predicate = false;
                if (write_buffer.load()->GetUsedBufferSize())
                    <<< WRITE TO DEDICATED HARDWARE >>>
                write_lk.unlock();
            } while (!quit_predicate);
        }
    private:
        Bar bar_buffers[2];
        Bar* primary_buffer, *secondary_buffer;
        std::atomic<bool> write_predicate, quit_predicate;
        std::atomic<Bar*> write_buffer;
        std::mutex foo_lock, write_lock;
        std::thread foo_thread;
        std::condition_variable begin_write;
};

0 个答案:

没有答案