Indeed, starvation mode is just a way to ensure fairness, at the cost of surrendering control and being woken up later. Mutexes can be costly when there is a lot of competition, it is up to us to use it correctly
Who's "us"? In Go, the threads are a shared resource across all the code. Multiple libraries can be contending for a resource.
I've hit something like this in Wine's storage allocator. Wine has its own "malloc", not the Microsoft one. It has three nested locks, and the innermost one is a raw spinlock. When a buffer is resized ("realloc"), and a new buffer has to be allocated, the copying is done inside a spinlock. If you have enough threads doing realloc calls (think "push") the whole thing goes into futex congestion collapse and performance drops by two orders of magnitude.
A spinlock in user space is an optimistic assumption. It's an assumption that the wait will be very short. It's important to have something tracking whether that assumption is working. At some load level, it's time to do an actual lock and give up control to another thread. "Starvation mode" may be intended to do that. But it seems more oriented towards preventing infinite overtaking.
By “us”, I was referring to normal, everyday go devs (versus working on go itself).
I think starvation mode is a pragmatic way of solving the issue: overtaking happens when the fast path assumption is wrong - but absolute fairness is not a requirement. So it’s ok. The gains of the fast path is enough to justify having that starvation mode.
I would argue that if some piece of code ends up wasting lots of time on futex calls, it suggests that this code was not designed with the right use case in mind.
Interesting comment about wine’s allocator, did not know about this
Animats|1 year ago
I've hit something like this in Wine's storage allocator. Wine has its own "malloc", not the Microsoft one. It has three nested locks, and the innermost one is a raw spinlock. When a buffer is resized ("realloc"), and a new buffer has to be allocated, the copying is done inside a spinlock. If you have enough threads doing realloc calls (think "push") the whole thing goes into futex congestion collapse and performance drops by two orders of magnitude.
A spinlock in user space is an optimistic assumption. It's an assumption that the wait will be very short. It's important to have something tracking whether that assumption is working. At some load level, it's time to do an actual lock and give up control to another thread. "Starvation mode" may be intended to do that. But it seems more oriented towards preventing infinite overtaking.
Can you push Go's mutexes into futex congestion?
lcof|1 year ago
I think starvation mode is a pragmatic way of solving the issue: overtaking happens when the fast path assumption is wrong - but absolute fairness is not a requirement. So it’s ok. The gains of the fast path is enough to justify having that starvation mode.
I would argue that if some piece of code ends up wasting lots of time on futex calls, it suggests that this code was not designed with the right use case in mind.
Interesting comment about wine’s allocator, did not know about this