I'm searching for some guiding principles to apply when using container data structures with Boost.ASIO. The Boost.ASIO documentation describes how strand objects can be used to provide serialized access to shared resources without explicit thread synchronization. I am looking for a systematic way to apply strand synchronization to:
- STL (or STL-like) containers (e.g., std::deque,std::unordered_map); and
- wait-free containers such as boost::lockfree::spsc_queueorfolly::ProducerConsumerQueue.
My questions are listed below. I should mention that my main questions are 1-3, but with justification I am also prepared to accept "These questions are moot/misguided" as an answer; I elaborate on this in question 4.
- To adapt an arbitrary STL container for safe synchronized use, is it sufficient to perform all its operations through a - strandinstance?
- To adapt a wait-free read-write container for synchronized, concurrent use, is it sufficient to wrap its operations through two distinct - strands, one for read operations and one for write operations? This question hints at a "yes", although in that use case the author describes using a- strandto coordinate producers from several threads, while presumably only reading from one thread.
- If the answer to 1-2 above is yes, should the - strandjust manage operations on the data structure through calls to- boost::asio::post?
To give an idea of the issue in 3., here is a snippet from the chat client example:
void write(const chat_message& msg)
{
  boost::asio::post(io_context_,
      [this, msg]()
      {
        bool write_in_progress = !write_msgs_.empty();
        write_msgs_.push_back(msg);
        if (!write_in_progress)
        {
          do_write();
        }
      });
}
Here, write_msgs_ is a queue of chat messages. I ask because this is a bit of a special case where the call to post can potentially invoke a composed async operation (do_write). What if I just wanted to push or pop from a queue? To take a highly simplified example:
template<typename T>
class MyDeque {
public:
     push_back(const T& t);
    /* ... */
private:
    std::deque<T> _deque;
    boost::asio::io_context::strand _strand
};
Then should MyDeque::push_back(const T& t) just call
boost::asio::post(_strand, [&_deque]{ _deque.push_back(t); })
and similarly for other operations? Or is boost::asio::dispatch a more appropriate option?
- Finally, I know that there are a plenty of robust implementations of concurrent vectors, hash maps, etc. (e.g., Intel Thread Building Blocks Containers). But it seems that under restricted use cases (e.g., just maintaining a list of active chat participants, storing recent messages, etc.) that the force of a fully concurrent vector or hash map may be a bit overkill. Is this indeed the case or would I be better off just using a fully concurrent data structure?
 
    