I'm building a DAG scheduler and have to guard against threads adding tasks while other threads are executing them. To this end, tasks look something like
struct Task {
dependencies: AtomicUsize,
dependents: Mutex<Vec<Arc<Task>>>,
// blah blah
}
When creating a task, the task-generation thread adds itself to every non-finished dependency as follows:
for dep in deps {
match dep.0.dependents.try_lock() {
// If we acquire the lock, then add ourselves as a dependant and increase the
// dependency count.
Ok(mut x) => {
new_task.dependencies.fetch_add(1, Ordering::Acquire);
x.push(new_task.clone());
}
// If we fail to acquire the lock, then the dependent task has already completed
// and notified its dependents. However, its data is immediately available for
// use, so we don't update our dependency count.
Err(TryLockError::WouldBlock) => {}
_ => unreachable!(),
}
}
// All dependencies are immediately available, schedule new_task on a threadpool
if new_task.dependencies.load(Ordering::Release) == 0 {
//
}
When executing a task, another thread does the following:
// ... Execute the task
// At this point, our output has been written so we can notify our dependencies
// their data is available. Dependents contending on this lock can just back off
// and immediately use the data.
let mut deps = task.dependents.lock().unwrap();
// Notify our dependents that we've finished, which means our output buffer is
// available for use.
while let Some(dep) = deps.pop() {
if dep.dependencies.fetch_sub(1, Ordering::Release) == 1 {
// Dispatch `dep` on a threadpool
}
}
// When this instruction retires, keep the mutex locked so any future dependents
// will just be able to immediately use our data.
std::mem::forget(deps);
The idea is that the task creation thread can check for dependency completion based on whether or not its dependents mutex is locked, and the std::mem::forget
on the mutex guard call keeps it locked after the task finishes.
Is forget
ting a MutexGuard
a bad idea? In particular, does Rust guarantee anything about what happens when you drop the underlying Mutex
while locked? pthread_mutex_destroy's documentation on Linux suggests this is undefined behavior, but Rust on Linux uses Futex, which is just an AtomicBool (and dropping it should be fine).