Go Sync.Cond, the Most Overlooked Sync Mechanism

11 pointsposted 11 hours ago
by thunderbong

3 Comments

jhanschoo

8 hours ago

Only earlier today I'd just used Sync.Cond. The article writes

> In theory, a condition variable like sync.Cond doesn’t have to be tied to a lock for its signaling to work. > You could have the users manage their own locks outside of the condition variable, which might sound like it gives more flexibility. It’s not really a technical limitation but more about human error.

I don't think this is true, if the concurrency model for goroutines may be assumed to be the same as regular OS threads. Assume the following:

1. You lock a shared resource (to be progressed by a worker goroutine)

2. You spawn the worker goroutine

3. You unlock the shared resource

4. It just so happens that the go runtime schedules the worker on a separate CPU, the worker now then progresses the resource to completion, and cond.Broadcast()'s

5. You now cond.Wait'() on a signal that will never arrive. (This cannot actually happen because Golang's cond.Wait() requires the lock to have been acquired before being called, and also locks before relinquishing control, so we're discussing the post author's version)

This was also the issue I noticed when I was considering using channels for signaling. The use case was:

1. I needed a worker goroutine that progressed a certain resource

2. I needed to fail anyway if the worker did not progress to completion at least within a timeout.

3. I needed to eagerly return asap if the worker progressed to completion before the timeout is done.

I initially considered spawning the worker goroutine and a timeout goroutine and doing a select on channels. But it turned out to be far easier done with a condition variable, especially if we needed to make sure that nothing eventually ends up blocking on sending to a channel that will never be listened to.

gpderetta

7 hours ago

Indeed, that's the basic missed wake-up scenario for a signaling primitive.

With condition variables (as a general primitive, not specifically in Go) checking the for the condition and waiting must be done atomically, by holding the lock, as condition variables are stateless.

Evencounts and futexes (aka keyed events) are similar edge triggered primitives, but they don't require an user visible lock to be held as atomicity is guaranteed in other ways.

user

9 hours ago

[deleted]