This is the mail archive of the
libstdc++@gcc.gnu.org
mailing list for the libstdc++ project.
Re: Memory barriers vs lock/unlock
- From: "Peter Dimov" <pdimov at mmltd dot net>
- To: "Paolo Carlini" <pcarlini at suse dot de>,"libstdc++" <libstdc++ at gcc dot gnu dot org>
- Cc: "Jonathan Wakely" <cow at compsoc dot man dot ac dot uk>,"Alexander Terekhov" <alexander dot terekhov at gmail dot com>
- Date: Tue, 8 Nov 2005 21:22:14 +0200
- Subject: Re: Memory barriers vs lock/unlock
- References: <4370AEC8.4020504@suse.de>
Paolo Carlini wrote:
Hi,
in our simple port of boost_shared_ptr we have some naively puzzling
things like:
void
release() // nothrow
{
if (__gnu_cxx::__exchange_and_add(&_M_use_count, -1) == 1)
{
dispose();
__glibcxx_mutex_lock(_M_mutex);
__glibcxx_mutex_unlock(_M_mutex);
weak_release();
}
}
void
weak_release() // nothrow
{
if (__gnu_cxx::__exchange_and_add(&_M_weak_count, -1) == 1)
{
__glibcxx_mutex_lock(_M_mutex);
__glibcxx_mutex_unlock(_M_mutex);
destroy();
}
}
I'm currently investigating that, and I'm not an expert of this area,
and I'd like to have some help. If I remember correctly some old
exchanges those "weird" empty lock/unlock stem from the need to add
memory barriers, nothing more.
Correct. The sequence is thread A invoking release(), dropping the last
strong reference, then thread B invoking weak_release(), dropping the last
weak reference. Thread B's destroy() needs to observe the effects of thread
A's dispose().
It's possible to optimize release a bit by inlining weak_release by hand:
void
release() // nothrow
{
if (__gnu_cxx::__exchange_and_add(&_M_use_count, -1) == 1)
{
dispose();
__glibcxx_mutex_lock(_M_mutex);
__glibcxx_mutex_unlock(_M_mutex);
if (__gnu_cxx::__exchange_and_add(&_M_weak_count, -1) == 1)
{
destroy();
}
}
}
There's no need to lock the mutex twice.
Therefore, I'm wondering whether we
wouldn't be best off using right away _GLIBCXX_READ_MEM_BARRIER and
_GLIBCXX_WRITE_MEM_BARRIER like, for instance, libsupc++/guard.cc is
already doing.
_GLIBCXX_READ_MEM_BARRIER is a #loadLoad barrier; _GLIBCXX_WRITE_MEM_BARRIER
is #storeStore. Neither is a full barrier. However, since __exchange_and_add
is a read-modify-write operation (both a load and a store), a combination of
_GLIBCXX_READ_MEM_BARRIER and _GLIBCXX_WRITE_MEM_BARRIER can be used instead
of the lock/unlock pair. I think. :-)
When __exchange_and_add is implemented in terms of __sync_fetch_and_add,
which seems to guarantee full ordering, there'll be no need for lock+unlock
or explicit barriers.