Стендап Сьогодні 📢 Канал в Telegram @stendap_sogodni

🤖🚫 Контент вільний від AI. Цей пост на 100% написаний людиною, як і все на моєму блозі. Насолоджуйтесь!

12.06.2024

Кооперативна рівночасність

З цього відео зрозумів фундаментально важливий аспект рівночасності у Swift.

Рівночасність з Grand Central Dispatch була витискальною. Це стандартна модель рівночасності, де операційна система планує потоки та перемикає виконання як їй зручно. На ній побудовані всі сучасні ОС, та рівночасні аспекти більшості мов є абстракцією над потоками операційної системи.

Витискальна рівночасність чудово працює, поки потоків не так багато, та вони незалежні один від одного. Втім, з приходом рівночасного програмування в маси зʼявилися нові потреби. Що, якщо ми хочемо завантажити 1000 файлів, та в кожного з них буде рівночасний обробник? Робити 1000 потоків? Всі, хто так робив, швидко дізнаються, що у кількості потоків є технічні обмеження, та варто користуватися пулом, щоб на 1000 задач було лише декілька потоків (дехто каже, стільки, скільки ядер в процесора.)

Але пул потоків — то суто технічна необхідність, ускладнення програми. Тому деякі мови програмування вносять пул потоків у власну платформу та надають нам абстракції вищого рівня — це називається кооперативна рівночасність. Кооперативною, бо планувальник тепер знаходиться на рівні мови, та здатний перемикати задачі згідно з мовними конструкціями. Наприклад, з гарантією, що перемикання не відбудеться посередині синхронного коду. Або, що актор ніколи не матиме дві паралельні задачі.

От як раз у Swift async/await це механізм кооперативної рівночасності. Всі задачі з async/await виконуються на пулі потоків, та перемикання між ними відбувається лише там, де є ключове слово await. Ба більше, перехід async/await мало відрізняється від звичайного виклику функції — тільки тим, що стек замінюється новим. Та якщо синхронізація готова відбутися в момент виклику — то виконання просто продовжиться, без жодних пауз.

Це дуже крута та сучасна система. Сходу не знаю інших мов, де async/await може прямо передавати контроль (пишіть, якщо знаєте.) Наприклад, в JavaScript кожний await повертає нас у цикл подій; в Golang синхронізація через канали ніби завжди блокує одну сторону, бо це не виклик функції; в Ruby з ракторами так само.