Using Tasks 101

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

Feedback and corrections appreciated!

25 Likes

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 :slight_smile:

1 Like

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

1 Like

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.

4 Likes

Got it
And what would be examples of higher level synchronization concepts?

1 Like

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.

2 Likes

And what would be examples of higher level synchronization concepts?

Channel, Threads.@spawn, and @sync are the main ones built in to Julia.

For higher-level options you can look at OhMyThreads.jl or ThreadsX.jl, both of which are under the JuliaFolds2 organization: JuliaFolds2 · GitHub

2 Likes

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.

1 Like

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.

See the Concurrency part of Operating Systems: Three Easy Pieces for a general traditional overview. For semaphores in particular, see The Little Book of Semaphores by AB Downey.

There was also this fun game, hope the Web site is still functional:

1 Like

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?

In fact, @async appears 45 times in the Julia 1.10.4 manual and 48 times in the one for 1.11.0-rc1. Quite a bit for a deprecated macro!

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?

Probably? When I asked about it, I was told “for new code there is no reason to use @async.

It might be worth mentioning ThreadSafeDicts.jl as well.

Fwiw: GitHub - carstenbauer/LittleBookOfSemaphores.jl: Julia code snippets inspired by the Little Book Of Semaphores

1 Like