Coroutines реализуют
кооперативную многозадачность между green_threads внутри одного процесса (ОС обо всем этом не догадывается, ее потоки не имеют к этому никакого отношения). В момент "вызова" сoroutine происходит переключение "зеленых" задач вместо собственно вызова функции. Задачи, ожидающие завершение ввода/вывода, получают управление, если их ввод/вывод завершен (или таймаут истек).
Для чего все это нужно? Потоки ОС слишком тяжелые. Они занимают много стека (памяти). Их создание/уничтожение требует времени. Переключение между ними происходит через ядро ОС (слишком медленно). Большое количество, одновременно активных, потоков может подвесить всю систему. В реальных приложениях/серверах количество потоков типично измеряется максимум десятками ("зеленых" потоков могут быть десятки тысяч). Для преодоления проблемы
C10K назрела необходимость переходить на асинхронное программирование с "зелеными" потоками вместо потоков ОС. Осталась одна проблема - переключение задач (которое теперь должно осуществляться вручную); кто и в какой момент должен это делать?
Это переключение может происходить в io_loop (цикле ввода/вывода) специальной библиотеки, которая отвечает за асинхронность всего приложения (например, asyncio в python). А чтобы поток управления из кода возвращался в io_loop, нужно заменить прямые вызовы функций на асинхронные "вызовы" короутин.
В начале 90-x вытесняющая многозадачность на процессорах 80386 казалась совершенно прорывной технологией. Кто бы мог подумать, что через 20-25 лет мы (в каком-то смысле) вернемся назад к кооперативной многозадачности.