代码之家  ›  专栏  ›  技术社区  ›  Anna B

并行设计困难

  •  1
  • Anna B  · 技术社区  · 14 年前

    我有一个名为root的类,它用作动态方法调用的电话簿:它保存指向对象的URL键字典。当一个命令想要执行一个给定的方法时,它用一个URL和一些参数调用一个根实例:

    root_->call("/some/url", ...);
    

    实际上,根目录中的调用方法与此类似:

    // Version 0
    const Value call(const Url &url, const Value &val) {
      // A. find object
      if (!objects_.get(url.path(), &target))
        return ErrorValue(NOT_FOUND_ERROR, url.path());
      }
    
      // B. trigger the object's method
      return target->trigger(val);
    }
    

    从上面的代码中,您可以看到这个“call”方法是 线程安全:“目标”对象可以在A和B之间删除,我们不能保证“对象”成员(字典)在我们阅读时不会被更改。

    我想到的第一个解决方案是:

    // Version I
    const Value call(const Url &url, const Value &val) {
      // Lock Root object with a mutex
      ScopedLock lock(mutex_);
    
      // A. find object
      if (!objects_.get(url.path(), &target))
        return ErrorValue(NOT_FOUND_ERROR, url.path());
      }
    
      // B. trigger the object's method
      return target->trigger(val);
    }
    

    这很好,直到“target->trigger(val)”是一个需要通过更改对象的URL或插入新对象来更改根的方法。修改作用域并使用rw mutex会有所帮助(在根目录上读取的内容远多于写入的内容):

    // Version II
    const Value call(const Url &url, const Value &val) {
      // A. find object
      {
        // Use a RW lock with smaller scope
        ScopedRead lock(mutex_);
        if (!objects_.get(url.path(), &target))
          return ErrorValue(NOT_FOUND_ERROR, url.path());
        }
      }
      // ? What happens to 'target' here ?
    
      // B. trigger the object's method
      return target->trigger(val);
    }
    

    “目标”发生了什么?我们如何确保在查找和呼叫之间不会删除它?

    一些想法:对象删除可以在根目录的消息队列中进行后置。但是,我们需要在完整方法范围内删除另一个rw mutex read locking,并使用一个单独的线程来处理删除队列。

    所有这些对我来说都是非常复杂的,我不确定并发设计是否必须看起来像这样,或者我只是没有正确的想法。

    ps:代码是一个名为 oscit (打开声音控制)。

    2 回复  |  直到 14 年前
        1
  •  2
  •   Community prosti    7 年前

    为了避免删除“target”,我必须写一个 线程安全引用计数的智能指针 . 这并不难。您需要确保的唯一一件事是在关键部分内访问引用计数。见 this post 更多信息。

        2
  •  1
  •   Hans Passant    14 年前

    你这样做是错误的。记住:您不能锁定数据,只能阻止代码。不能用本地定义的互斥体保护“objects”成员。您需要在代码中使用完全相同的互斥体来更改对象集合。它必须在另一个线程执行call()方法时阻止该代码。mutex必须至少在类范围内定义。