https://ru.wikipedia.org/wiki/Сопрограмма
Собственно producer у вас и есть сопрограмма, их особенность в том, что они в любое время могут вернуть управление в вызывающий их код, при этом сохранив свое состояние, а потом можно опять вызвать эту сопрограмму и она продолжит выполнение с этого места.
алгоритм такой(представим, что код функций send и receive расположен в там, где их вызывают, просто чтобы мне писать меньше):
1. консьюмер в цикле вызывает сопрограмму "продюсер"
2. продюсер получает данные
3. с помощью yield возвращает данные в кнсьюмера(это как return, только с возможностью потом продолжить выполнение сопрограммы с этого места)
4. консьюмер выводит данные и опять вызывает сопрограмму(шаг 1. т.к. бесконечный цикл) с того места, где она остановилась(а там тоже бесконечный цикл, шаг 2)