News for hackers. Stuff that matters.
Re:Discipline Doesn’t Scale by jandrewrogers on Tuesday October 20, 2020 @10:39PM (#24844292)
I’m not sure “what locks it” is a useful question. Locking is a performance and scalability destroying operation in a time when we care about both. Systems that care about both largely avoid locking (including most “lock-free” locks) altogether outside of rare cases, and in such rare cases the logic is simple. Nothing is lost by avoiding locks with good architecture.

In big multi-core systems, I model the handful of locks by the worst-case access frequency for a core to understand contention. In practice, this is often on the order of 100ms, which means the locks are effectively contention-free and the fast path almost always wins.

Re:Discipline Doesn’t Scale by Animats on Wednesday October 21, 2020 @12:41AM (#24844784)
"What locks it" can include "owned by different threads". That's a lock at a higher level, really.

"Lock free" data structures are tied very closely to the data being locked, and depend heavily on certain CPU operations being atomic. This is more of an issue with ARM than with x86, because ARM needs more fence instructions. Really, "lock free" programming is just locking critical sections with lower level hardware primitives.

The opposite extreme is POSIX locks. Locks are the OS's problem, and nothing in the code says what's covered by the lock. That was once standard practice and is now recognized as being bad.

Re:Discipline Doesn’t Scale by scott_s on Wednesday October 21, 2020 @11:06AM (#24848551)
> Really, "lock free" programming is just locking critical sections with lower level hardware primitives.

I have to disagree, as the more interesting lock free algorithms use atomic operations that can fail. Yes, a compare-and-swap is like having a critical section on modifying that particular address. But a compare-and-swap can fail if what is currently there is unexpected. The result of that failure generally means redoing a bunch of work, rather than just trying again. Interesting lock free algorithms tend to have the structure:

1. Read some data.

2. Perform computations on that data.

3. Trying to commit the result of that computation. If someone else committed data to the same location after you read it in step 1, go back to step 1.

That's less like a "critical section," and more like a transaction. For lock free algorithms that just rely on atomic operations that cannot fail (such as an atomic increment), then yes, those are just like having a lower-level critical section. (And, on some architectures, that's exactly what they are.)

Re:Discipline Doesn’t Scale by yencabulator on Wednesday October 21, 2020 @04:35PM (#24851877)
Optimistic locking is still locking. The low level compare-and-swap operation still uses CPU cacheline-level locking. https://en.wikipedia.org/wiki/MESI_protocol
Re:Discipline Doesn’t Scale by scott_s on Wednesday October 21, 2020 @07:20PM (#24853366)
What's important is that you no longer have a critical section, where mutual exclusion is enforced. The only piece of code that has mutual exclusion is an attempt to commit the transaction. The kind of thinking required to implement such algorithms is quite different than when you can rely on mutual exclusion.

(Also, not all architecture use hardware mechanisms to enforce actual mutual exclusion. For example, Power does not, as it uses load-linked, stored-conditional: https://en.wikipedia.org/wiki/Load-link/store-conditional. On Power, even things like atomic increments are implemented with LL/SC. Architectures basically have to decide: will they implement atomics through some kind of a lock mechanism, or some kind of LL/SC mechanism.)

Re:Discipline Doesn’t Scale by yencabulator on Wednesday October 21, 2020 @10:11PM (#24854267)
You still have a critical section; the cacheline is in an exclusive mode during the change. LL/SC operates on top the cache sharing protocols, just like everything else. Your wikipedia link even refers to that: "Typically, CPUs track the load-linked address at a cache-line or other granularity [...]". MESI and friends are that mechanism.

What lock-free means in this case is that an OS level thread freezing doesn't prevent global progress. That's true on the abstraction level of typical programming, but grandparent is very much correct:

> Really, "lock free" programming is just locking critical sections with lower level hardware primitives.

Re:Discipline Doesn’t Scale by Animats on Wednesday October 21, 2020 @03:09PM (#24851003)
compare-and-swap

Compare and swap is interesting. It's a tiny transaction. It can even operate across networks - FireWire had hardware compare and swap support. Compare and swap with retry may have to wait and retry, which is effectively waiting for someone else to stop using a resource you want. So it's a form of a critical section lock.

Re:Discipline Doesn’t Scale by clktmr on Wednesday October 21, 2020 @07:25AM (#24846703)
> Really, "lock free" programming is just locking critical sections with lower level hardware primitives

Can fence instructions really be seen as locking mechanism? While a lock generally is bound to some data, memory fences are bound to relationships between data or a piece of code. I don't know if Rust or any other language can help you with that.

Isn't the actual bad practice to expose locks at all? If the locking is done implicitly in API calls, one doesn't need to ask "What locks it".

Re:Discipline Doesn’t Scale by jpab on Wednesday October 21, 2020 @08:39AM (#24847188)
The fences aren't a locking mechanism, at least not on their own. The lower (hardware) level locks are the steps in the cache coherency protocols (potentially including explicit cache line locking signals) that allow an execution unit to, eg, perform a correct atomic compare-exchange operation on a 32/64/128-bit piece of data.

See also, eg, the LOCK prefix on x86 instructions.

(disclaimer: I only have approximate knowledge of anything, I'm not an expert)

Re:Discipline Doesn’t Scale by scott_s on Wednesday October 21, 2020 @11:13AM (#24848604)
Agreed. But! Not all architectures implement atomic operations by locking the bus, as x86 does. The Power architecture uses load-linked, stored-conditional (https://en.wikipedia.org/wiki/Load-link/store-conditional) where when you load a value, you can say "pay attention to who modifies this value," and when you go to store the value, you can say "only actually store this value if no one else modified it."
Re:Discipline Doesn’t Scale by adwn on Wednesday October 21, 2020 @02:01AM (#24845148)
> Locking is a performance [...] destroying operation

That is incorrect. Properly designed and implemented mutexes (like in the parking_lot crate [1]) are extremely fast in the uncontented case, and need just one byte of overhead (and no heap-allocated memory) per mutex. In the contented case, a lock-free algorithm still has to deal with arbitration between the different threads, so it's typically not faster than a lock-based approach. Lock-free algorithms do have certain advantages (like deadlock-freedom), but performance is typically not one of them.

> Locking is a [...] scalability destroying operation

Also incorrect. If you have N threads trying to append to a single queue, then you have a scalability problem whether you're using locked or lock-free data structures. The solution in such a case is fine-grained concurrency and a different approach, not lock-free algorithms.

[1] https://github.com/Amanieu/parking_lot

Re:Discipline Doesn’t Scale by dkersten on Wednesday October 21, 2020 @05:47AM (#24846203)
> it's typically not faster than a lock-based approach

A common train of thought seems to be "locks are slow, so lock free must be fast", while lock-free says nothing about performance -- only about forward progress.

Re:Discipline Doesn’t Scale by rocqua on Wednesday October 21, 2020 @07:41AM (#24846806)
The question is not about actual software locks.

The question is "What prevents me from making changes to this variable without breaking everything".

So the point is not "What locks guard this data" the point is, how do I determine whether I can currently modify (or even read) this data. This allows the compiler to check that, indeed, you never use the data in a way that would break things. Hence your compiler can then give guarantees about thread-safety.

Now it seems likely that, when compilers do this and you write wrong code, your conclusion might be "I need a mutex lock for this", or if performance matters, "I need to rewrite this to allow performant lockless code. But the main question remains "Can I currently modify this data without issues".