Should std::sync::Condvar have the Sync trait?


#1

I’ve been playing with rust for about 2 weeks now, and it’s interesting. The nice thing is once the compiler allows the code to compile, it usually works correctly, so I’ve had to do very little debugging.

I’m trying to synchronize threads and using the Condvar object. Should it have the Sync trait? It doesn’t which tells me from what I’ve read that I need to protect access, yet the example doesn’t surround it with a Mutex. Granted by the nature of the element you tend to have a mutex at least while waiting on it, but the mutex isn’t explicitly protecting the object.

Or maybe I should be protecting it with it’s own mutex? I guess the big question is am I required to acquire the same mutex or acquire a mutex at all to call notify_one() and notify_all()?


#2

I am not familiar with Rust’s Condvar specifically, but it seems to works like POSIX condition variables.

Conditions are useless by themselves, they are only useful in combination with some shared mutable state.

The subtle thing about condition variables is that the “condition” they are about is abstract: it is not a specific variable or a memory area or anything of the kind, it is a mathematical proposition built on the variables of the program.

A typical use looks like that (pseudo-code):

while nb_jobs_to_do == 0 {
    wait_on_condition();
}
job = take_next_job();
nb_jobs_to_do--;

The wait_on_condition() is associated with the condition “nb_jobs_to_do == 0”, or probably any condition based on the value of nb_jobs_to_do. It returns each time another thread signals it, and that must be each time another raisses the nb_jobs_to_do.

But you can not be certain the condition will actually be fullfilled. Consider the following sequence:

  • thread 1 waits on condition;
  • thread 2 increases the variable and signals the condition;
  • thread 1 wakes up;
  • thread 3 decreases the variable;
  • thread 1 tries to use the variable, assuming it is not 0: crash.

Therefore, the normal way of using a condition is in a loop, like I wrote earlier.

And of course, since the code uses shared variables (nb_jobs_to_do), it must be protected by a mutex. You can see that waiting on a condition variable takes a mutex: this is the one.

Consider this: you could do a poor’s man version of waiting on a condition like that:

while nb_jobs_to_do == 0 {
    unlock(nb_jobs_to_do);
    sleep(0.1);
    lock(nb_jobs_to_do);
}

This is called polling, and is doubly evil. A condition variable is meant to replace exactly that, by waking at the precise time the condition changes instead of after an arbitrary amount of time. And as you can see, the condition needs to be able to unlock and relock the mutex: otherwise, the condition could never change.


#3

Okay, reading the documentation on the pthread function pthread_cond_signal, it says you should call it after the mutex lock is acquired. So I’m guessing that means it’s not required, just recommended.

I was probably over thinking things and starting to worry about CPU cache synchronization for memory locations that are not explicitly protected by the mutex.


#4

Condvar is Sync (and also Send). Otherwise, you wouldn’t be able to put it in an Arc and pass that into a thread at all, as you can see happens in the example. Rust’s approach to safety is to make it an error to do something unsafe. Send and Sync are automatically implemented for most types, so there doesn’t need to be an explicit implementation in order for a type to implement them. There’s a limited number of types which need to explicitly opt-in.


#5

[quote=“stevenblenkinsop, post:4, topic:2230”]
Send and Sync are automatically implemented for most types, so there doesn’t need to be an explicit implementation in order for a type to implement them.
[/quote]To expand on that, “automatically” means that there’re default implementations of those traits for any types that don’t opt out (see OIBIT RFC). The confusing part is rustdoc hasn’t been taught to indicate that a type is eligible for that default implementation.


#6

Okay thank you, I was checking that and for Mutex Sync was mentioned in the documentation probably because it needed to “opt_in” but Condvar it wasn’t listed.


#7

Here is why you usually want to involve the mutex in the sender side of a condition variable in some way, in any language.


#8

You can’t actually change the condition without using the mutex, though, since the shared mutable state is only accessible through the mutex, so there’s no pitfall in Rust here.


#9

Rust doesn’t enforce that whatever condition you check in your wait loop is actually based on the data inside the mutex…


#10

comex: this stackoverflow thread is partially wrong: it is perfectly ok to signal the condition variable without holding the mutex. What is not acceptable is to change the shared state (condition = TRUE in their code) without holding the mutex. Rust will not let you do that anyway.
Opinions are mixed about what is best between releasing the mutex before or after signaling; my opinion is it depends on the exact operations the program does.