The things that are described there also work together / can be used in combinations.
For example semaphores, channels, async-channels, threads and many other things can be used as syncronizable events, which means that you can use sync
to wait for multiple things at the same time and also handle them individually with handle-evt
this is useful for threads that collect results from multiple other threads or for threads that are used as "control" threads (listening for multiple inputs / receiving control messages / then possibly creating or terminating worker threads etc.)
Async-channels are useful as work queues because multiple threads can put to and get from them, so they can be used to distribute work or collect results. When you create them with a limited max size they can be useful as a buffer between producers and consumers, for example if your producers are faster then your consumers, an unlimited async-channel would keep growing taking more and more memory if you limit it to number of consumers + a few more (so that consumers always have work items waiting for them) then producers are blocked/paused whenever consumers are too slow.
(Why would you want multiple consumers in a non parallel, concurrent szenario? For example in a webscraper, where every consumer processes a link by downloading it and sends the links it finds to a found-links-queue that is processed by a thread (which has a set of seen links) that adds the link to the work queue if it wasn't seen yet. Downloading is often mostly, concurrently with other downloads, waiting for input.)
When you pair your data so that mostly threads own the data they need to access, then you don't have to worry about other threads while accessing that data. Keep in mind that threads are pretty light weight and you can create many of them and give each what it needs, on construction or for example by receiving it via thread-receive
.
Semaphores are useful when you want to make something look atomic to multiple threads, while also allowing them to read/write fairly arbitrary. Doing this may seem like an easy solution (and I also use it), but it is very possible that this creates a lot more contention between the different threads, then is necessary with a less hammer like solution.
The other things I wrote are mostly about trying to avoid that situation. (Because when you can avoid multiple writers things get easier, especially if you also want parallelism not only concurrency, where contention really becomes problematic)
I will add a disclaimer here that I haven't done much parallelism with racket yet.
If you have specific questions about certain code patterns (with examples) and what could be useful there, it is easier to discuss those, then making "general" claims.