开发者

Difficult concurrent design

I have a class called Root which serves as some kind of phonebook for dynamic method calls: it holds a dictionary of url keys pointing to objects. When a command wants to execute a given method it calls a Root instance with an url and some parameter:

root_->call("/some/url", ...);
开发者_运维技巧

Actually, the call method in Root looks close to this:

// 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);
}

From the code above, you can see that this "call" method is not thread safe: the "target" object could be deleted between A and B and we have no guarantee that the "objects_" member (dictionary) is not altered while we read it.

The first solution that occurred to me was:

// 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);
}

This is fine until "target->trigger(val)" is a method that needs to alter Root, either by changing an object's url or by inserting new objects. Modifying the scope and using a RW mutex can help (there are far more reads than writes on Root):

// 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);
}

What happens to 'target' ? How do we ensure it won't be deleted between finding and calling ?

Some ideas: object deletion could be post-poned in a message queue in Root. But then we would need another RW mutex read-locking deletion on the full method scope and use a separate thread to process the delete queue.

All this seems very convoluted to me and I'm not sure if concurrent design has to look like this or I just don't have the right ideas.

PS: the code is part of an open source project called oscit (OpenSoundControl it).


To avoid the deletion of 'target', I had to write a thread safe reference counted smart pointer. It is not that hard to do. The only thing you need to ensure is that the reference count is accessed within a critical section. See this post for more information.


You are on the wrong track with this. Keep in mind: you can't lock data, you can only block code. You cannot protect the "objects" member with a locally defined mutex. You need the exact same mutex in the code that alters the objects collection. It must block that code when another thread is executing the call() method. The mutex must be defined at least at class scope.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜