【问题标题】:Sharing data between API and application threads在 API 和应用程序线程之间共享数据
【发布时间】:2018-08-05 08:05:44
【问题描述】:

我的 API 在自己的线程中计算一些数据:

/*** API is running in its own thread ***/
class API {
public:
    std::shared_ptr<Data> retrieveData() { return mData; }

private:
    std::shared_ptr<Data> mData;
    std::mutex mDataMutex;

    void run () {
        std::thread t([](){
            while (!exitApi) {
                mDataMutex.lock();

                updateData(mData);

                mDataMutex.unlock();
        });
        t.join();
    }
};

使用我的 API 的应用程序将在另一个线程中检索共享数据:

/*** Application is running in another thread ***/
class Application {
private:
    Api mApi;

    void run () {
        std::thread t([](){
            while (!exitApp) {
                std::shared_ptr<Data> data = mApi.retrieveData();

                /* API thread can update the data while the App is using it! */
                useData(data);
        });
        t.join();
    }

如何设计我的 API 以使应用程序开发人员在检索数据时不会陷入困境?我能想到三个选项,但不喜欢其中任何一个:

  1. API 将返回所有数据的副本,而不是共享指针。但是,数据量可能会变得非常大,因此应避免复制。
  2. API 在将数据交给应用程序时会锁定数据,应用程序需要在执行所有计算后明确要求 API 再次解锁。即使正确记录,这也很容易出现死锁。
  3. 当 API 将数据交给应用程序retrieveData 时,也会返回一个已经锁定的std::unique_lock。一旦应用程序使用完数据,它必须解锁unique_lock。这可能不太容易出错,但对于应用程序开发人员来说仍然不是很明显。

有没有更好的选择来设计尽可能对开发人员友好的 API(在现代 C++11 及更高版本中)?

【问题讨论】:

    标签: c++ multithreading c++11 c++17 api-design


    【解决方案1】:

    TL;DR:将shared_ptr 与调用解锁的自定义删除器一起使用。

    它认为两种主要方法是:

    1. 返回一个不可变的数据结构,以便它可以在线程之间共享。这是一个干净的 API,但(如前所述)复制可能很昂贵。一些减少复制需求的方法是:

      • 使用写时复制数据结构,这样每次只需要复制部分数据。根据您的数据,这可能不是 可能,否则重构工作量太大。
      • 尽可能使用移动引用来降低复制成本。仅此一项可能还不够,但取决于您的实际数据。
    2. 在可变数据结构周围使用锁。正如所指出的,这需要 API 用户执行可能不明显的额外操作。但是可以使用智能指针来减轻消费者的负担:

      • 一种更简单的方法是返回一个自定义智能指针,该指针 在其析构函数中解锁:当调用者的作用域关闭时会发生解锁,因此调用者无需担心解锁调用。 API 使用者将通过引用其方法来传递指针。例如func(locking_ptr&amp; ptr)。一个简单的实现可以在这里找到:https://stackoverflow.com/a/15876719/1617480
      • 为了能够通过复制而不是通过引用来传递锁定智能指针,需要采用某种引用计数方案。您可能希望在锁定智能指针内部使用shared_ptr,以避免滚动您自己的线程安全引用计数。更简单地将自定义删除器传递给 shared_ptr 以解锁和删除(根本不需要编写智能指针)。
      • 另一种类型的智能指针将围绕锁中的每个取消引用-&gt;。我认为这不适合这个用例,因为看起来 API 使用者想要一个一致的结果视图。以下是此类指针的示例:https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Execute-Around_Pointer

    【讨论】:

    • 非常感谢您的好回答!它激发了我可以在 shared_ptr 的自定义删除器中解锁互斥锁的想法。在我看来,这是解决问题的一种优雅方式。
    • 使用自定义删除器很棒。我已将其添加到答案中。
    【解决方案2】:

    另一种方法涉及闭包,例如添加

    // inside class API
    void do_locked(std::function<void(Data*)>fun) {
      std::guard_lock<std::mutex> gu(mDataMutex);
      fun(mData);
    }
    

    您可以使用lambda expression 调用它

    //inside Application::run2
    mApi.do_locked([=](Data *dat) { useData(dat); });
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-11-03
      • 2019-03-29
      • 1970-01-01
      • 2018-11-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多