Benefits of cooperative multitasking

This is not the way to understand this.

On all of our supported platforms, Swift co-exists with C and is built on top of an underlying C runtime. In particular, all work done by Swift is actually running on some C thread. C threads are typically scheduled preemptively, usually directly by the kernel, and there is always a potential for there to be threads in a process that are not running Swift code. Thus, at a low level, all Swift work is scheduled preemptively.

C threads are not a scalable resource. If you try to make a very large number of C threads in a process, you will quickly exhaust resources (mostly memory) in both the kernel and in user space, and subsequent attempts to create threads will fail. Swift concurrency is therefore designed around the principle that it is bad practice to hold on to a thread for an indefinite period if you're not currently doing any work (i.e. a task wants to block on some arbitrary condition). Instead, the thread should be released to do useful work; when the task becomes unblocked, it can request to be scheduled back onto a thread. Swift, by default, schedules tasks onto a strictly-limited pool of threads, and it only ever interrupts a task at a dynamic "suspension point", such as when a function calls or returns to a function with different actor isolation. This can be seen as a kind of cooperative scheduling: Swift expects code to cooperate by following the principle above, on the pain of global effects that would not be possible under strict preemptive scheduling, up to and including thread starvation. But it is not cooperative scheduling in the way that that term has traditionally often been used (e.g. in macOS classic), because the work is still executing on a thread which can both be preempted for and run in parallel with other threads in the same process.

Regardless of the details about scheduling, tasks and actors always run sequentially "internally", and you do not need to worry about concurrency or parallelism within a single task or actor. Only one thread can ever be running with a particular actor isolation at a time, and if the actor "migrates" between threads, those threads will synchronize with each other to create a total order of the events on the actor. Similarly, if a task needs to change threads, those threads will synchronize to create a total order of the events on the task.

21 Likes