It doesn't prevent it, but it can help with synchronization.
If you have two coroutines writing to the same variable, but they yield to each other, you know they won't ever both run at the same time.
You also know if you spawn multiple coroutines that they won't yield except where they call yield, so everything before and after the yield you know will be atomic.
> If you have two coroutines writing to the same variable, but they yield to each other, you know they won't ever both run at the same time.
But that won’t be parallel just concurrent, and in case of cooperative “threads”, you could have probably written it in a more readable single threaded way, as that’s pretty much just calling two functions back and forth.
Your second point also only works when you have a single thread of execution, otherwise concurrency will entail parallelism and all the usual problems will become apparent.
Threads still have the issue of synchronization and atomicity even when only concurrent and not parallel.
That is, assuming you had a single core CPU, with threads you'd still need to synchronize things when implementing concurrency. Coroutines have a more explicit synchronization from their natural ping/pong as you yield which could be said to tend to be safer in the average case.
I think you're maybe conflating something. If two things write to the same global variable for example, that can never be parallel, but it can be concurrent. With threads, the writes to the variables need to be guarded with some synchronization mechanisms, if you forget you'll have bugs.
With coroutines, they will be naturally synchronized by the yield points.
> you could have probably written it in a more readable single threaded way, as that’s pretty much just calling two functions back and forth
It's not just calling two functions back and forth, the coroutines retain state and continue where they yielded. Each time they yield they do not consume additional stack frames.
If you have two coroutines writing to the same variable, but they yield to each other, you know they won't ever both run at the same time.
You also know if you spawn multiple coroutines that they won't yield except where they call yield, so everything before and after the yield you know will be atomic.