lundi 31 juillet 2017

Force copy (then destroy) on move-only type

Consider the following function of a lock-free work-stealing (de)que:

template<class T>
inline T WorkStealQ<T>::Steal(void)
{
    auto tail{_tail.load(std::memory_order_acquire)};
    if (_head.load(std::memory_order_acquire) <= tail) return T();
    auto task{_tasks[tail & _mask]};
    if (_tail.compare_exchange_weak(tail, tail + 1, std::memory_order_release, std::memory_order_relaxed)) return task;
    return T();
}

But what if T is only moveable but non-copyable? The issue is that the reading of the item from the buffer is a copy operation, and cannot be changed to auto task{std::move(_tasks[tail & _mask])}; because another concurrent operation could also move it, in which case any move constructor which is not read-only but also modifies the original (such as nulling some pointer to a resource) would break the algorithm.

Note that the overall semantics of Steal() do perform only a move from an external point of view, since only one concurrent operation will return with the T that was stored at that location; any others who lose the race would fail the compare_exchange_weak(). Thus, the operation doesn't break the semantics of a moveable only T as far as the user is concerned. Unfortunately, internally it needs to make a temporary shallow copy of T until it determines whether to complete it as a move or give up, leaving it in the buffer (it's basically a two-phase move with a check occurring in the middle).

One way to do this would be make copy constructor and copy assignment private members of T and have a friend WorkStealQ. The problem is what to do in the case of third-party library classes I may want to use as T. Is there any other option in that case than just using pointers to such objects rather than storing them intrusively (and thus getting a performance hit)? I'm assuming memcpy won't work even for a shallow copy in the case of classes with virtual functions.

Aucun commentaire:

Enregistrer un commentaire