Search code examples
c++multithreadingthread-safetytbb

Is tbb::concurrent_bounded_queue::size thread unsafe?


Source has following comment:

// Return the number of items in the queue; thread unsafe
std::ptrdiff_t size() const {
    return my_queue_representation->size();
}

But then I looked at internal impl, and it all seems to be thread safe(operations are loads of std::atomic and then some substraction).

std::ptrdiff_t size() const {
    __TBB_ASSERT(sizeof(std::ptrdiff_t) <= sizeof(size_type), NULL);
    std::ptrdiff_t hc = head_counter.load(std::memory_order_acquire);
    std::ptrdiff_t tc = tail_counter.load(std::memory_order_relaxed);
    std::ptrdiff_t nie = n_invalid_entries.load(std::memory_order_relaxed);

    return tc - hc - nie;
}

Is the comment just wrong, or am I missing something?


Solution

  • It is thread-safe in the sense that the behavior is well-defined by the C++ standard, i.e. has no race conditions as defined in the standard. It is not thread-safe in the sense that the result is an atomic snapshot of the queue, i.e. is not a sequentially consistent implementation.

    Allowing a negative size was a deliberate design decision. A negative size of N indicate there are N pending pops. Old versions of TBB have this comment:

    //! Integral type for representing size of the queue.
    /** Note that the size_type is a signed integral type.
        This is because the size can be negative if there are pending pops without corresponding pushes. */
    typedef std::ptrdiff_t size_type;
    

    The latest version seems to be missing the comment. (I was the original architect for TBB but no longer work for Intel, so I can't speak for the intent of the current version.)

    When the queue is quiescent, the returned value is accurate, and when it is not quiescent it may be inaccurate. In practice, implementing size() to return an atomic snapshot would not add much value, because if the queue is active, the returned value might become inaccurate immediately afterwards. That's why there are operations such as try_push and try_pop instead of, as in regular STL, letting the user inspect the queue and then act on it as separate operations.