A key reason C and C++ allow spurious failures in try_lock has nothing to do with weak compare-exchange. The problem appears when someone writes code like this where x is an ordinary non-atomic variable:
Thread 1:
x = 42;
lock(l);
...
Should the compiler be allowed to move the assignment of x into the critical section that starts with the lock? It's often a desirable compiler optimization; in fact, compilers sometimes do that (compilers moving unsynchronized code into a critical section is generally fine; moving code out of a critical section is definitely not fine). However, prohibiting spurious failures breaks this optimization. The problem arises if there is another thread that does the following:
Thread 2:
while (try_lock(l) == success) {
unlock(l);
}
assert(x == 42);
Section 3 of Hans Boehm's paper on the rationale behind the C++ memory model explains this problem in detail and why C/C++ chose to let them spuriously fail.
I'm not sure what memory model Swift uses. The choices (assuming a program contains unsafe code that relies on mutexes for safety) are to either:
- Allow try_lock to fail.
- Define lock/unlock/try_lock as not always being sequentially consistent in the memory model to disallow code like this.
Either one works. Choice 2 has a significant usability cost, since almost everyone is taught that they are sequentially consistent.