I’m not sure if this is the right place to post this, but I spent a lot of time learning about Tasks recently for a project at work, and wrote up a blog post tutorial summarizing how to use them: https://www.lesswrong.com/posts/kPnjPfp2ZMMYfErLJ/julia-tasks-101
I really appreciate this post! Especially as someone who needed several attempts to wrap my head around both the concepts and the nomenclature of tasks/threads/coroutines/… (in general, not particularly in Julia).
Given that the reader already knows about the existence of hardware/OS threads (which I think is a given if one is looking for this kind of information), it’s straight to the point and very accessible
I liked your post, very useful! Thanks for sharing!
With thread-safe you basically mean avoiding data race between threads, right? Or something else?
Have you ever had the need of using Atomic operations to avoid data race conditions? In the Julia documentation atomic operations can become handy in these cases
Atomic operations are a very low-level thing, basically reserved for concurrency experts. In the majority of cases one should instead use higher level synchronization concepts.
With thread-safe you basically mean avoiding data race between threads, right? Or something else?
I mean a slightly more general definition of thread-safety. If you try to write to a Dict from multiple threads, you won’t get races, you’ll get a segfault. From Wikipedia: " a function is thread-safe when it can be invoked or accessed concurrently by multiple threads without causing unexpected behavior, race conditions, or data corruption."
Have you ever had the need of using Atomic operations to avoid data race conditions?
I haven’t used Atomic – Channels are a higher-level concept, that along with Tasks are (theoretically) general enough express all concurrent computations.
Atomics seem more like they’re oriented towards language developers, or developers of particularly high performance libraries – they only work on primitive times, only support a limited range of operations, etc.
While Channels are great for serializing access to shared resources like a common Dict, you could add a chapter about locks. It is somewhere between atomics and channels, and fairly easy to understand. What to use depends on how much overhead one can afford. Atomics isn’t very complicated either, except the fine points of memory ordering semantics.
Atomics are at the lowest level. Above them are locks and semaphores, above those are condition variables (currently not available in Julia, AFAIK) and Channel.
Your blog post says @async is deprecated? Julia’s official manual on Network and Streams uses @async in some examples. Should the examples be updated with @spawn as a drop-in replacement?
While Channel s are great for serializing access to shared resources like a common Dict , you could add a chapter about locks.
A post about this would be interesting for sure, but I’ve had a lot of trouble trying to write correct code with locks. E.g. we left code like this in our codebase for months, which was causing deadlocks in notebooks about once a week:
function setindex!(tsd::ThreadSafeDict, k, v)
lock(tsd.slock)
h = setindex!(tsd.d, k, v)
unlock(tsd.slock)
return h
end
In contrast when I wrote a Channel version of a thread-safe Dict it just worked.
Your blog post says @async is deprecated? Julia’s official manual on Network and Streams uses @async in some examples. Should the examples be updated with @spawn as a drop-in replacement?