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