__sync_val_compare_and_swap vs __sync_bool_compare_and_swap

I was thinking about the return values ​​of these two functions. The return value of the __sync_bool_compare_and_swap function seems to have obvious advantages, that is, I can use it to determine if a swap has occurred. However, I don't see a good use of the __sync_val_compare_and_swap return value.

First, let's have a function signature to reference (from the GCC docs minus var args):

type __sync_val_compare_and_swap (type *ptr, type oldval type newval);

      

I see that the return value of __sync_val_compare_and_swap is the old * ptr value. To be precise, this is the value that was seen as a result of the implementation of this function after the appropriate memory barriers were set. I state this clearly to satisfy the fact that between the call to __sync_val_compare_and_swap and the execution of the instructions to enforce the memory barrier, the * ptr value can easily change.

Now that the function returns, what can I do with this return value? There is no point in trying to compare it to * ptr, because * ptr can now be changed to other streams. Likewise, comparing newval and * ptr doesn't really help me (unless I'm blocking * ptr, which probably undermines my use of atomatics in the first place).

So all I have left to do is ask if the returned value == oldval, which is efficient (see below for caveat), is asking if a swap operation took place. So I could just use __sync_bool_compare_and_swap.

The caveat I just talked about is that the only small difference I see here is that it doesn't tell me if an exchange has taken place or not, it just tells me that at some point before the memory barrier was released * ptr has the same meaning as newval. I am considering the possibility that oldval == newval (although I would go out of my way to effectively implement a function to check these values ​​first and not change them if they were the same, so this is probably a moot point). However, I don't see a situation where knowing this difference would affect me on the call site. In fact, I cannot imagine a situation where I would set oldval and newval equal.

My question is:

Is there any use case where the use of __sync_val_compare_and_swap and __sync_bool_compare_and_swap would not be equivalent, i.e. there is a situation where one provides more information than the other?

ASIDE

The reason I thought about it was because I found an implementation of __sync_val_compare_and_swap in terms of sync_bool_compare_and_swap that has a race:

inline int32_t __sync_val_compare_and_swap(volatile int32_t* ptr, int32_t oldval, int32_t newval)
{
    int32_t ret = *ptr;
    (void)__sync_bool_compare_and_swap(ptr, oldval, newval);
    return ret;
}

      

Race when saving * ptr to ret, as * ptr might change before calling __sync_bool_compare_and_swap. This made me realize that I do not seem to be safe (without additional barriers or locks) to implement __sync_val_compare_and_swap in terms of sync_bool_compare_and_swap. This made me think that the former should provide more "information" than the latter, but from my question, I don't see that this is really the case.

+3


source to share


1 answer


The operation provided __sync_val_compare_and_swap

can always be implemented in terms __sync_bool_compare_and_swap

(and of course another direction is possible), so they are equivalent in cardinality. However, the implementation __sync_val_compare_and_swap

in terms is __sync_bool_compare_and_swap

not very efficient. It looks something like this:

for (;;) {
    bool success = __sync_bool_compare_and_swap(ptr, oldval, newval);
    if (success) return oldval;
    type tmp = *ptr;
    __sync_synchronize();
    if (tmp != oldval) return tmp;
}

      

More work is needed because you can observe the crash __sync_bool_compare_and_swap

, but then read the new value from *ptr

that happens from oldval

.



As for why you might prefer behavior __sync_val_compare_and_swap

, the value that caused the failure can give you a starting point to retry the operation more efficiently, or it can indicate a meaningful cause for the failure of some operation that will not be "redone". See the code pthread_spin_trylock

in musl libc (for which I am the author) as an example :

http://git.musl-libc.org/cgit/musl/tree/src/thread/pthread_spin_trylock.c?id=afbcac6826988d12d9a874359cab735049c17500

There is a_cas

equivalent __sync_val_compare_and_swap

. This is a silly example in a way, since it just stores a branch or conditional move using the old value, but there are other situations where multiple old values ​​are possible and knowing what caused the error to operate.

+3


source







All Articles