I have a callable which performs an iterative calculation, which is otherwise thread-safe. I would like to keep a statistic of the mean number of iterations that it used, and is thread-safe.
Stylized code follows:
mutable struct OnlineMean
count::Int
mean::Float64
OnlineMean() = new(0, 0.0)
end
function update!(om::OnlineMean, x)
om.count += 1
om.mean += (x - om.mean) / om.count
nothing
end
struct MyCallable
… # other fields
mean_iterations::OnlineMean
end
# this function called from various tasks
function (mc::MyCallable)(z)
y, iterations = do_the_work_and_count(mc, z) # assume this is thread-safe
update!(mc.mean_iterations, iterations) # QUESTION: make this thread-safe
y
end
Is this as simple as putting a SpinLock in OnlineMean and using it in update!? Or should I somehow (how?) use Channels to pass in updates, or atomic operations…
Spinlock is simple, and since the critical section is small, probably not too much at risk for starvation.
Channel has a lock internally, so it’s the same but with more memory overhead (and more complicated to control, since you need the one writer task that you need to maintain).
Atomics are probably optimal in terms of overhead, but they’ll be a bit tricky here since you need to update two things that can’t be written atomically at the same time due to both being 8 bytes large. Consider getting some inspiration from Sequence counters and sequential locks — The Linux Kernel documentation , the technique there can probably be adapted to your usecase, depending on how frequent you read/write data.
I’d use a ReentrantLock here by wrapping OnlineMean in Lockable. It’s probably good enough. Spinlocks are tricky to use and I doubt they provide much benefit over a ReentrantLock, but they do cause issues if you change the code so it can yield while the lock is taken.
I can’t think of a way to do this atomically, unless you instead store the sum and the count instead of the count and the mean.