How Programming Languages Model Asynchronous Program Flow 30 to be configured, they're still fixed in size and can't grow or shrink. They are, after all, the cause of stack overflows, which will be a problem if you configure them to be too small for the tasks you're running. If we have many small tasks that only require a little stack space but we reserve much more than we need, we will occupy large amounts of memory and possibly run out of it. Context switching As you now know, threads and schedulers are tightly connected. Context switching happens when the CPU stops executing one thread and proceeds with another one. Even though this process is highly optimized, it still involves storing and restoring the register state, which takes time. Every time that you yield to the OS scheduler, it can choose to schedule a thread from a different process on that CPU. Y ou see, threads created by these systems belong to a process. When you start a program, it starts a process, and the process creates at least one initial thread where it executes the program you've written. Each process can spawn multiple threads that share the same address space. That means that threads within the same process can access shared memory and can access the same resources, such as files and file handles. One consequence of this is that when the OS switches contexts by stopping one thread and resuming another within the same process, it doesn't have to save and restore all the state associated with that process, just the state that's specific to that thread. On the other hand, when the OS switches from a thread associated with one process to a thread associated with another, the new process will use a different address space, and the OS needs to take measures to make sure that process “ A ” doesn't access data or resources that belong to process “B”. If it didn't, the system wouldn't be secure. The consequence is that caches might need to be flushed and more state might need to be saved and restored. In a highly concurrent system under load, these context switches can take extra time and thereby limit the throughput in a somewhat unpredictable manner if they happen frequently enough. Scheduling The OS can schedule tasks differently than you might expect, and every time you yield to the OS, you're put in the same queue as all other threads and processes on the system. Moreover, since there is no guarantee that the thread will resume execution on the same CPU core as it left off or that two tasks won't run in parallel and try to access the same data, you need to synchronize data access to prevent data races and other pitfalls associated with multicore programming. Rust as a language will help you prevent many of these pitfalls, but synchronizing data access will require extra work and add to the complexity of such programs. We often say that using OS threads to handle concurrency gives us parallelism for free, but it isn't free in terms of added complexity and the need for proper data access synchronization.