[concurrency-interest] jdk9 VarHandle and Fence methods

Oleksandr Otenko oleksandr.otenko at oracle.com
Wed Sep 16 11:52:28 EDT 2015


It is even ok to assume a failing CAS doesn't provide any fences. The 
code will have to use volatiles to communicate anything, if it never 
retries a failing CAS - and still the JVM should be able to eliminate 
the unnecessary store barriers on platforms where the failing CAS 
provides such a barrier anyway. So I would not demand the failing CAS to 
have fencing semantics.

Yet I do expect the CAS to only ever fail if the underlying value has 
been modified by another thread and no other circumstances (eg not 
because of a cache line invalidated because of a write to another 
location on the same line). Otherwise a boolean return value is too 
small for efficient cooperative concurrency.

In the same way a tryLock should only ever fail if some other thread 
held the lock at the time the acquire was attempted - in the meaning of 
system-wide progress: if I failed to acquire the lock, I should be able 
to conclude someone else is making progress; debug thread is not making 
progress, so it is changing the meaning of the lock.

Alex

On 16/09/2015 08:12, Peter Levart wrote:
>
>
> On 09/16/2015 03:26 AM, Vitaly Davidovich wrote:
>>
>> You'd differentiate via whatever protocol is chosen by 
>> implementation, i.e. some state that would be set to signal phase 
>> changes.  The closest analog is code using CAS, successful or not, to 
>> piggyback ordering for surrounding memory ops.
>>
>
> You can piggyback ordering of surrounding memory ops if you can reason 
> about the phase changes using the state manipulated by CAS even in 
> case of a failed CAS if that failure is guaranteed to not be spurious. 
> It's the same with lock, but only if the tryLock succeeds. I argue 
> that you can not reason about the phase of other thread(s) on the 
> failed tryLock even if that is not a spurious failure. A non-spurious 
> tryLock failure simply means that some thread has a lock, but does not 
> tell which one.
>
>>   I gave an example upthread where threads compete to get exclusive 
>> right to close a socket using tryLock; one thread wins and closes the 
>> socket, and losers skip closing the socket but proceed to use memory 
>> set inside unlock().  It's a semi contrived example since normally 
>> you'd use a CAS for something like this, but it illustrates the gist 
>> of what I'm getting at.  Of course you can solve these situations 
>> using dedicated atomic (or otherwise threadsafe) state, but perhaps 
>> one wants to piggyback on existing critical section's fences and not 
>> add additional ones.
>>
>
> Ah, I see. The following example, right?
>
> On 09/04/2015 05:09 AM, Vitaly Davidovich wrote:
>>
>> If thread A releases a lock and threads B and C tryLock it, with one 
>> succeeding, the failing thread may want to do something else but 
>> wants a happens-before edge with the lock release - that's the 
>> general use case.  As a simple example, consider two threads 
>> tryLock'ing to acquire the exclusive right to close a socket and then 
>> perform some additional actions that require ordering of actions done 
>> by the releasing thread.  The thread failing to acquire the lock will 
>> skip closing the socket but will proceed to do some work that 
>> requires happens-before edge.  This is typically done using CAS, with 
>> one thread successfully flipping the state, and the others just skip 
>> that action that's guarded by the CAS, but can proceed with doing 
>> subsequent work.  In other words, one may want to piggyback on the 
>> unlock/tryLock to provide the ordering rather than introducing 
>> additional dedicated state for this.
>>
>
> That's exactly what I'm talking about. B or C failing tryLock doesn't 
> mean one of them succeeded (how would any of B or C know that the 
> other succeeded if there's no other communication between the two?). 
> It could simply mean that both failed because A hasn't released the 
> lock yet. If the later is the case, then any relaxed memory operations 
> performed by A before releasing the lock can be observed in B or C 
> failing tryLock in arbitrary order regardless of whether failed 
> tryLock "guarantees" ordering or not, because there hasn't been an 
> unlock in A yet. If those operations are not relaxed but follow some 
> release/acquire or SC semantics, then they operate by themselves and 
> don't require a failed tryLock to guarantee ordering.
>
> Regards, Peter
>
>> sent from my phone
>>
>> On Sep 15, 2015 4:52 PM, "Peter Levart" <peter.levart at gmail.com 
>> <mailto:peter.levart at gmail.com>> wrote:
>>
>>
>>
>>     On 09/15/2015 02:56 PM, Vitaly Davidovich wrote:
>>>
>>>     Hmm, the ordering I had in mind was unlock() happens-before a
>>>     failing tryLock.  So a thread failing on tryLock sees operations
>>>     preceded by last unlock() as ordered.  This is no different than
>>>     successful tryLock or lock() in that regard.
>>>
>>
>>     How can you differentiate between:
>>     - unlock() in thread T1 followed by unsuccessful tryLock() in
>>     thread T2 and
>>     - unsuccessfull tryLock() in T2 followed by unlock() in T1
>>
>>     You want T1 to already unlock before you observe tryLock()
>>     failing in T2 (pressumably because some T3 has already obtained
>>     the lock before T2 or because of spurious failure).
>>     But T2 failing tryLock() might simply be because T1 hasn't
>>     unlocked yet. So regardless of memory effects, you can't reason
>>     of ordering in other threads by observing tryLock() failing.
>>
>>     Do you have an example that proves me wrong?
>>
>>     Regards, Peter
>>
>>>     sent from my phone
>>>
>>>     On Sep 15, 2015 1:16 AM, "Hans Boehm" <boehm at acm.org
>>>     <mailto:boehm at acm.org>> wrote:
>>>
>>>         > How does it slow down lock()?
>>>
>>>         It depends on the precise guarantee you provide, and I
>>>         suspect this thread didn't quite agree on that.  The most
>>>         natural one is that the succeeding lock acquisition happens
>>>         before the failed trylock().  That implies that if we have
>>>
>>>         x = 1;
>>>         lock();
>>>
>>>         those can't be reordered by the hardware, since a failing
>>>         trylock() would have to see the assignment to x.  That
>>>         requires a fence between them on ARM or Power.
>>>
>>>         I think the right way to think of trylock(), at least
>>>         informally, is as allowing spurious failures. I.e. trylock()
>>>         is allowed to behave as though the lock was held when it
>>>         isn't.  You thus can't conclude anything about other threads
>>>         from the fact that it failed.  In this view you don't have
>>>         to think about memory ordering issues when reasoning about
>>>         correctness, you just reason about spurious failures instead.
>>>
>>>         If your code is robust against unknown, e.g. debugger,
>>>         threads acquiring the lock now and then, then it must be
>>>         robust against this sort of spurious failure.  If the lock
>>>         is really used only to provide mutual exclusion, this should
>>>         not affect correctness.
>>>
>>>         On Mon, Sep 14, 2015 at 6:41 PM, Vitaly Davidovich
>>>         <vitalyd at gmail.com> wrote:
>>>
>>>             How does it slow down lock()?
>>>
>>>             I don't necessarily disagree but I can certainly see
>>>             people considering tryLock to have same ordering effect
>>>             as (failed) CAS.  It's certainly true that a CAS is a
>>>             lower level primitive than a lock, but I don't know if
>>>             that resonates immediately when thinking about this. 
>>>             It's also the case that on very popular platforms such
>>>             as x86 a failing tryLock will have the same ordering as
>>>             a successful one, and no difference is observed (and JIT
>>>             doesn't do anything different).
>>>
>>>             I don't understand the debugger thread example - what's
>>>             the issue there?
>>>
>>>             sent from my phone
>>>
>>>             On Sep 14, 2015 9:07 PM, "Hans Boehm" <boehm at acm.org> wrote:
>>>
>>>                 FWIW, this general issues is discussed in section 3
>>>                 of http://dl.acm.org/citation.cfm?id=1375581.1375591 .
>>>
>>>                 Yet another argument against providing the stronger
>>>                 guarantees is that, on many architectures, it
>>>                 doesn't just slow down trylock(), it more
>>>                 importantly slows down lock().  In general, if your
>>>                 code cares about ordering for unsuccessful
>>>                 trylock(), then it's not robust against, say, a
>>>                 debugging thread unexpectedly acquiring the lock for
>>>                 a short period.  In my view, in such a case, you're
>>>                 no longer using it as a lock, and you should be
>>>                 using something else, e.g. an atomic object, with
>>>                 stronger guarantees.
>>>
>>>                 On Fri, Sep 4, 2015 at 4:18 AM, Doug Lea
>>>                 <dl at cs.oswego.edu> wrote:
>>>
>>>                     On 09/03/2015 02:19 PM, Oleksandr Otenko wrote:
>>>
>>>                         Has anyone come up with the answer about
>>>                         ordering for tryLock, or have I missed it?
>>>
>>>
>>>                     You missed the dog not barking :-)
>>>
>>>                     The Lock specs don't require any specific HB
>>>                     effects here on failed
>>>                     tryLock. Even if we wanted to, we cannot
>>>                     retroactively impose any
>>>                     considering that anyone can implement the Lock
>>>                     interface (not just j.u.c)
>>>                     and some of these might become in violation.
>>>
>>>                     As you and Vitaly pointed out, there are a few
>>>                     fringe cases where
>>>                     users might want to impose ordering on failure.
>>>                     In jdk9, you'll
>>>                     me able to do this with moded VarHandle accesses
>>>                     and/or fences. The
>>>                     resulting extra fencing might be redundant here
>>>                     and there, but if you
>>>                     cared enough, you could create and rely on
>>>                     custom locks with stronger
>>>                     guarantees.
>>>
>>>                     -Doug
>>>
>>>
>>>                     _______________________________________________
>>>                     Concurrency-interest mailing list
>>>                     Concurrency-interest at cs.oswego.edu
>>>                     <mailto:Concurrency-interest at cs.oswego.edu>
>>>                     http://cs.oswego.edu/mailman/listinfo/concurrency-interest
>>>
>>>
>>>
>>>                 _______________________________________________
>>>                 Concurrency-interest mailing list
>>>                 Concurrency-interest at cs.oswego.edu
>>>                 <mailto:Concurrency-interest at cs.oswego.edu>
>>>                 http://cs.oswego.edu/mailman/listinfo/concurrency-interest
>>>
>>>
>>>
>>>
>>>     _______________________________________________
>>>     Concurrency-interest mailing list
>>>     Concurrency-interest at cs.oswego.edu
>>>     <mailto:Concurrency-interest at cs.oswego.edu>
>>>     http://cs.oswego.edu/mailman/listinfo/concurrency-interest
>>
>
>
>
> _______________________________________________
> Concurrency-interest mailing list
> Concurrency-interest at cs.oswego.edu
> http://cs.oswego.edu/mailman/listinfo/concurrency-interest

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://cs.oswego.edu/pipermail/concurrency-interest/attachments/20150916/1c7753dc/attachment-0001.html>


More information about the Concurrency-interest mailing list