Limit Swift Concurrency's cooperative pool
One of the nice things about Swift Concurrency is its cooperative thread pool. When writting concurrent code you should not think about threads but is still good to know how things work under the hood.
In Swift Concurrency all your async code runs in a cooperative thread pool, unless you are using Actors (or in the future custom Executors, but that’s a topic for another day). The beauty of the cooperative pool abstraction is that it allows us to not care about thread exhaustion, or even about our code running “on the background”, something I still see many developers get wrong.
You may have seen this cooperative pool while debugging, pay attention to how Xcode shows a label in the debug navigator:
As you can see the thread is part of the cooperative concurrent queue.
We can use a bit of expensive code to use all the queues and force the runtime to create all possible threads. In my M1 that means 8 threads:
It’s actually quite nice to see all threads so nice and tidy ^^
The important thing here is that this is an implementation detail and you should not worry about it, as you should not worry about threads at all.
I know the irony of saying that you shouldn’t worry about threads in a post about threads 😂. But if you are having a hard time with Swift Concurrency is probably because you still haven’t made the switch to this new way of thinking. There are some legit cases to care about threads, but those are mostly for inter-operation with other systems that are thread based. Free yourself from the threads! 🔥 🧵
In fact this cooperative pool is such a specific implementation detail that it could work totally different in other operative systems or more constrained enviornments. The nice thing about this is that your code should work in the same way, albeit slower and less parallel, in a system with only 1 thread.
Remember that Swift Concurrency doesn’t make any promise about parallelism. Most of your code is serial with itself and concurrent with other blocks of code, but you can’t make it parallel, that depends on the runtime.
And you can test how your code would behave in such a constrained system thanks to an environment variable that limits the cooperative pool.
Set LIBDISPATCH_COOPERATIVE_POOL_STRICT=1
and see how the runtime will only create 1 thread on the pool.
You can set this in the Scheme editor in Xcode.
After setting that flag and running the same code that before was spawning 8 threads, we can see now how it only spawns 1.
There you have it, a nice utility to force the Swift runtime to use limit the cooperative pool to 1 thread, useful for some debugging situations and for learning purposes.