Чому мови програмування автоматично не керують синхронною / асинхронною проблемою?


27

Я не знайшов багато ресурсів з цього приводу: мені було цікаво, чи можливо / гарна ідея вміти писати асинхронний код синхронно.

Наприклад, ось код JavaScript, який отримує кількість користувачів, що зберігаються в базі даних (асинхронна операція):

getNbOfUsers(function (nbOfUsers) { console.log(nbOfUsers) });

Було б непогано мати можливість написати щось подібне:

const nbOfUsers = getNbOfUsers();
console.log(getNbOfUsers);

І тому компілятор автоматично піклується про очікування відповіді і потім виконання console.log. Він завжди буде чекати завершення асинхронних операцій, перш ніж результати доведеться використовувати деінде. Ми б набагато менше використовували обіцянки зворотного виклику, асинхронізувати / чекати чи що завгодно, і ніколи не доведеться хвилюватися, чи є результат операції доступним негайно чи ні.

Помилки все ще можуть бути керовані (чи nbOfUsersотримали ціле число чи помилку?) За допомогою try / catch або щось подібне до додаткових варіантів, наприклад, у мові Swift .

Це можливо? Це може бути жахлива ідея / утопія ... я не знаю.


58
Я не дуже розумію ваше запитання. Якщо ви "завжди чекаєте асинхронної операції", то це не асинхронна операція, це синхронна операція. Ви можете уточнити? Можливо, дайте конкретний тип поведінки, яку ви шукаєте? Крім того, "що ви думаєте про це" - це поза темою програмної інженерії . Вам потрібно сформулювати своє питання в контексті конкретної проблеми, яка має єдину, однозначну, канонічну, об'єктивно правильну відповідь.
Йорг W Міттаг

4
@ JörgWMittag я собі гіпотетичну C # , який неявно awaitса , Task<T>щоб перетворити йогоT
Caleth

6
Те, що ви пропонуєте, неможливо виконати. Не вирішувати компілятор, чи потрібно чекати результату чи, можливо, стріляти і забути. Або біжіть у фоновому режимі і чекайте пізніше. Навіщо обмежувати себе так?
каверзний

5
Так, це жахлива ідея. Просто використовуйте async/ awaitзамість цього, що робить асинхронні частини виконання явними.
Бергі

5
Коли ви говорите, що дві речі відбуваються одночасно, ви говорите, що це нормально, що ці речі відбуваються в будь-якому порядку. Якщо ваш код не може дати зрозуміти, що повторне замовлення не порушить очікувань вашого коду, він не може зробити їх одночасними.
Роб

Відповіді:


65

Асинхронізація / очікування - це саме те автоматизоване управління, яке ви пропонуєте, хоча і з двома додатковими ключовими словами. Чому вони важливі? Окрім зворотної сумісності?

  • Без явних пунктів, де може бути призупинено та відновлено використання програми, нам знадобиться система типу, щоб визначити, де потрібно чекати очікуваного значення. У багатьох мовах програмування немає такої системи типів.

  • Роблячи очікування значення явним, ми також можемо передавати очікувані значення навколо як об'єкти першого класу: обіцянки. Це може бути дуже корисно при написанні коду вищого порядку.

  • Асинхронний код має дуже глибокі ефекти для моделі виконання мови, схожі на відсутність або наявність у мові винятків. Зокрема, функцію асинхронізації можна очікувати лише за допомогою функцій асинхронізації. Це впливає на всі функції виклику! Але що робити, якщо ми змінимо функцію з не-асинхронні на асинхронні в кінці цього ланцюга залежностей? Це було б невідповідною зміною… якщо тільки всі функції не асинхронізовані і кожен виклик функції очікується за замовчуванням.

    І це дуже небажано, оскільки це має дуже погані наслідки для продуктивності. Ви не зможете просто повернути дешеві цінності. Кожен виклик функції став би значно дорожчим.

Асинхрон чудовий, але якась неявна асинхрія не працюватиме насправді.

У чистих функціональних мов, таких як Haskell, є трохи вилупного люка, тому що порядок виконання значною мірою не визначений та непомітний. Або по-різному: будь-який конкретний порядок операцій повинен бути чітко закодований. Це може бути досить громіздким для програм у реальному світі, особливо тих програм, які тяжко вводять / виходять, для яких асинхронний код дуже добре підходить.


2
Вам не обов'язково потрібна система типу. Прозорі майбутні, наприклад, ECMAScript, Smalltalk, Self, Newspeak, Io, Ioke, Seph, можуть бути легко реалізовані без системної або мовної підтримки. У Smalltalk та його нащадків об’єкт може прозоро змінювати свою ідентичність, у ECMAScript він може прозоро змінювати свою форму. Це все, що потрібно зробити Futures прозорим, не потрібно мовної підтримки для асинхронності.
Йорг W Міттаг

6
@ JörgWMittag Я розумію, про що ти говориш, і як це могло б працювати, але прозорі ф'ючерси без системи типів ускладнюють одночасно мати ф'ючерси першого класу, ні? Мені знадобиться якийсь спосіб вибрати, чи хочу я надсилати повідомлення в майбутнє чи значення майбутнього, бажано щось краще, ніж someValue ifItIsAFuture [self| self messageIWantToSend]через те, що це складно інтегрувати з загальним кодом.
амон

8
@amon "Я можу написати асинхронний код як обіцянки, так і обіцянки - монади". Тут монади насправді не потрібні. Грози по суті є лише обіцянками. Оскільки майже всі значення в Haskell знаходяться в полі, майже всі значення в Haskell є вже обіцянками. Ось чому ви можете кинути в parбудь-який куточок чистий код Haskell і отримати паралелізм безкоштовно.
DarthFennec

2
Асинхрон / очікування нагадує мені про монаду продовження.
les

3
Насправді і винятки, і асинхроніка / очікування є випадками алгебраїчних ефектів .
Алекс Рейнкінг

21

Те, що вам не вистачає, - це мета асинхронних операцій: вони дозволяють використовувати час очікування!

Якщо ви перетворите операцію з асинхронізацією, як-от запит деякого ресурсу з сервера, в синхронну операцію неявно і негайно чекаючи відповіді, ваш потік не може нічого більше робити з часом очікування . Якщо на сервер знадобиться 10 мілісекунд, щоб відповісти, витрачається близько 30 мільйонів циклів процесора. Затримка відповіді стає часом виконання запиту.

Єдина причина, через яку програмісти винайшли операції з асинхронізацією, - це приховати затримку суто тривалих завдань за іншими корисними обчисленнями . Якщо ви можете заповнити час очікування корисною роботою, це економний час процесора. Якщо ви не можете, ну, нічого не втрачається завдяки асинхронізації.

Тому я рекомендую прийняти операції з асинхронізацією, які надають вам ваші мови. Вони там, щоб заощадити ваш час.


я думав про функціональну мову, де операції не блокуються, тому навіть якщо він має синхронний синтаксис, тривале обчислення не перекриє нитку
Сінн,

6
@Cinn Я цього не знайшов у питанні, а прикладом у питанні є Javascript, який не має цієї функції. Однак, як правило, компілятору досить складно знайти значущі можливості для паралелізації, як ви описуєте: Значна експлуатація такої функції вимагає від програміста чітко подумати над тим, що вони ставлять відразу після тривалого затримки. Якщо ви зробите час виконання достатньо розумним, щоб уникнути цієї вимоги до програміста, ваш час виконання, швидше за все, знизить економію продуктивності, оскільки йому потрібно буде агресивно паралелізувати функціональні виклики.
cmaster

2
Усі комп'ютери чекають з однаковою швидкістю.
Боб Джарвіс - Відновіть Моніку

2
@BobJarvis Так. Але вони відрізняються тим, скільки роботи вони могли б зробити за час очікування ...
cmaster

13

Деякі так і роблять.

Вони не є мейнстрімом (поки що), тому що async - це відносно нова функція, яку ми лише зараз добре відчули, якщо це навіть хороша функція, або як представити її програмістам дружним / зручним / виразний / тощо. Існуючі функції асинхронізації значною мірою прикріплені до існуючих мов, які потребують дещо іншого підходу до дизайну.

Тим НЕ менше, це не ясно хороша ідея , щоб зробити всюди. Поширена помилка - це виконувати асинхронні дзвінки в циклі, ефективно серіалізуючи їх виконання. Якщо асинхронні дзвінки є неявними, це може приховати таку помилку. Крім того, якщо ви підтримуєте неявний примус від Task<T>(або еквівалента вашої мови) до T, це може додати трохи складності / вартості вашому інструменту перевірки набору тексту та повідомленню про помилки, коли незрозуміло, кого з двох програміст насправді хотів.

Але це не є непереборними проблемами. Якби ви хотіли підтримати таку поведінку, ви майже напевно могли, хоч були б компроміси.


1
Я думаю, що ідеєю можна було б обернути все в функціях асинхронізації, синхронні завдання просто вирішуватимуться негайно, і ми отримуємо всі єдині види впоратися (Редагувати: @amon пояснив, чому це погана ідея ...)
Cinn

8
Чи можете ви навести кілька прикладів для " Деякі з них "?
Бергі

2
Асинхронне програмування аж ніяк не нове, просто в даний час людям доводиться стикатися з цим частіше.
Кубічний

1
@Cubic - як мовна особливість, наскільки я знаю. До цього були лише (незручні) функції користувальницьких земель.
Теластин

12

Є мови, які роблять це. Але в цьому насправді не так вже й багато, оскільки це можна легко виконати за допомогою існуючих мовних особливостей.

Поки у вас є певний спосіб виразити асинхронність, ви можете реалізувати Futures або Promises виключно як функцію бібліотеки, вам не потрібні особливі мовні функції. І поки у вас є деякі виразити прозорі проксі , ви можете скласти дві функції разом, і у вас є прозорі майбутні .

Наприклад, у Smalltalk та його нащадків об’єкт може змінити свою ідентичність, він може буквально «стати» іншим об’єктом (а насправді називається метод, який це робить Object>>become:).

Уявіть давно обчислені обчислення, які повертають a Future<Int>. Це Future<Int>всі ті ж методи Int, що й за винятком різних реалізацій. Future<Int>«S +метод не додає інший номер і повертає результат, вона повертає новий , Future<Int>який обертає обчислення. І так далі, і так далі. Методи , які не можуть толково бути реалізовані шляхом повернення Future<Int>, замість цього автоматично awaitрезультатом, а потім викликати self become: result., який зробить виконувану в даний момент об'єкт ( self, тобто Future<Int>) в буквальному сенсі стати resultоб'єктом, тобто тепер посилання на об'єкт , який використовується , щоб бути Future<Int>в тепер Intскрізь, повністю прозорий для клієнта.

Не потрібні спеціальні мовні функції, пов'язані з асинхронією.


Добре, але це проблеми, якщо обидва Future<T>і Tспільний інтерфейс, і я використовую функціонал з цього інтерфейсу. Чи повинен це becomeрезультат, а потім використовувати функціонал, чи ні? Я думаю про такі речі, як оператор рівності або представлення налагодження для наведення рядків.
амон

Я розумію, що це не додає жодних особливостей, річ у тому, що у нас є різні синтаксиси, щоб писати негайно розв’язуючі обчислення та тривалі обчислення, і після цього ми використовували результати так само і для інших цілей. Мені було цікаво, чи можемо ми мати синтаксис, який прозоро обробляє обоє, роблячи це більш читабельним, і тому програмісту не потрібно з цим обробляти. Як і робити a + b, обидва цілі числа, незалежно від того, чи доступні a і b негайно чи пізніше, ми просто пишемо a + b(це можливо зробити Int + Future<Int>)
Cinn

@Cinn: Так, ви можете це зробити за допомогою прозорих ф'ючерсів, і для цього вам не потрібні особливі мовні функції. Ви можете реалізувати його, використовуючи вже наявні функції, наприклад, Smalltalk, Self, Newspeak, Us, Korz, Io, Ioke, Seph, ECMAScript, і, очевидно, як я щойно читав, Python.
Йорг W Міттаг

3
@amon: Ідея прозорого майбутнього полягає в тому, що ви не знаєте, що це майбутнє. Чи не з вашої точки зору, немає загального інтерфейсу між Future<T>і Tтому , що з вашої точки зору, немаєFuture<T> , тільки T. Зараз, звичайно, існує безліч проблем з питань інженерії щодо того, як зробити це ефективно, які операції слід блокувати проти неблокуючих тощо, але це справді незалежно від того, чи це ви робите як мову чи як функцію бібліотеки. Прозорість була вимогою, передбаченою ОП у питанні, я не заперечую, що це важко і може не мати сенсу.
Йорг W Міттаг

3
@ Jörg Це здається, що це було б проблематично ні в чому, окрім функціональних мов, оскільки ви не можете дізнатися, коли код реально виконується в цій моделі. Це, як правило, працює гарно в Haskell, але я не бачу, як це буде працювати на більш процедурних мовах (і навіть в Haskell, якщо ви дбаєте про ефективність, вам іноді доводиться змушувати виконувати та розуміти основну модель). Цікава ідея все-таки.
Ву

7

Вони роблять (ну, більшість із них). Функція, яку ви шукаєте, називається потоками .

Однак у ниток є свої проблеми:

  1. Оскільки код можна призупинити в будь-який момент , ви ніколи не можете припустити, що все не зміниться «само собою». Програмуючи з нитками, ви витрачаєте багато часу на роздуми про те, як у вашій програмі має справу з тим, що змінюється.

    Уявіть, що ігровий сервер обробляє атаку гравця на іншого гравця. Щось на зразок цього:

    if (playerInMeleeRange(attacker, victim)) {
        const damage = calculateAttackDamage(attacker, victim);
        if (victim.health <= damage) {
    
            // attacker gets whatever the victim was carrying as loot
            const loot = victim.getInventoryItems();
            attacker.addInventoryItems(loot);
            victim.removeInventoryItems(loot);
    
            victim.sendMessage("${attacker} hits you with a ${attacker.currentWeapon} and you die!");
            victim.setDead();
        } else {
            victim.health -= damage;
            victim.sendMessage("${attacker} hits you with a ${attacker.currentWeapon}!");
        }
        attacker.markAsKiller();
    }
    

    Через три місяці гравець виявляє, що, вбиваючись та відходячи точно під час attacker.addInventoryItemsбігу, тоді victim.removeInventoryItemsне вдасться, він може зберігати свої предмети, а нападник також отримує копію своїх предметів. Він робить це кілька разів, створюючи мільйон тонн золота з повітря і розбиваючи економіку гри.

    Крім того, зловмисник може вийти, поки гра надсилає повідомлення жертві, і він не отримає тегу "вбивці" над головою, тому його наступна жертва не втече від нього.

  2. Оскільки код можна призупинити в будь-якій точці , вам потрібно користуватися блокуваннями скрізь, коли маніпулюють структурами даних. Я наводив приклад вище, який має очевидні наслідки в грі, але він може бути більш тонким. Подумайте про додавання елемента до початку пов’язаного списку:

    newItem.nextItem = list.firstItem;
    list.firstItem = newItem;
    

    Це не проблема, якщо ви говорите, що потоки можна призупиняти лише тоді, коли вони роблять введення-виведення, а не в будь-який момент. Але я впевнений, ви можете уявити собі ситуацію, коли відбувається операція вводу / виводу - наприклад, реєстрація даних:

    for (player = playerList.firstItem; player != null; player = item.nextPlayer) {
        debugLog("${item.name} is online, they get a gold star");
        // Oops! The player might've logged out while the log message was being written to disk, and now this will throw an exception and the remaining players won't get their gold stars.
        // Or the list might've been rearranged and some players might get two and some players might get none.
        player.addInventoryItem(InventoryItems.GoldStar);
    }
    
  3. Оскільки код можна призупинити в будь-якій точці , потенційно врятувати його може багато. Система займається цим, надаючи кожному потоку абсолютно окремий стек. Але стек досить великий, тому у 32-розрядної програми не може бути більше 2000 потоків. Або ви могли зменшити розмір стека, ризикуючи зробити його занадто маленьким.


3

Знаходимо багато відповідей тут оманливих, оскільки, хоча питання буквально задавали питання про асинхронне програмування, а не про блокування IO, я не думаю, що ми можемо обговорити одну, не обговорюючи іншу в цьому конкретному випадку.

Хоча асинхронне програмування є властивим, добре, асинхронним, причиною асинхронного програмування є, головним чином, уникнення блокування потоків ядра. Node.js використовує асинхронність через зворотний виклик або Promises, щоб дозволити операції блокування відсилати з циклу подій, а Netty в Java використовує асинхронність через зворотні виклики або CompletableFutures, щоб зробити щось подібне.

Проте неблокуючий код не вимагає асинхронності . Це залежить від того, наскільки ви готові зробити мову програмування та час виконання.

Ідіть, Ерланг та Хаскелл / GHC, це вдається вирішити вам. Ви можете написати щось на кшталт var response = http.get('example.com/test')і дозволити йому випустити нитку ядра за кадром, очікуючи на відповідь. Це робиться за допомогою гарантій, процесів Ерланг або forkIOвідпускання потоків ядра поза кадром під час блокування, що дозволяє йому робити інші речі, очікуючи на відповідь.

Це правда, що мова насправді не справляється з асинхронністю для вас, але деякі абстракції дозволяють вам йти далі, ніж інші, наприклад, непередбачені продовження чи асиметричні умови роботи. Однак основну причину асинхронного коду, що блокує системні виклики, можна абсолютно абстрагувати від розробника.

Node.js та Java підтримують асинхронний неблокуючий код, тоді як Go та Erlang підтримують синхронний неблокуючий код. Обидва вони є дійсними підходами з різними компромісами.

Мій досить суб'єктивний аргумент полягає в тому, що ті, хто сперечається з режимами виконання, які керують неблокуванням від імені розробника, схожі на тих, хто сперечається проти вивезення сміття на початку. Так, це вимагає витрат (в даному випадку в першу чергу більше пам'яті), але це спрощує розробку та налагодження, а також робить кодові бази більш надійними.

Я особисто стверджую, що асинхронний неблокуючий код повинен бути зарезервований для системного програмування в майбутньому, а більш сучасні стеки технологій повинні переходити на синхронні неблокуючі умови для розробки додатків.


1
Це була справді цікава відповідь! Але я не впевнений, що я розумію ваше відмінність між «синхронним» та «асинхронним» неблокувальним кодом. Для мене синхронний неблокуючий код означає щось на зразок функції C на зразок такої, waitpid(..., WNOHANG)що не працює, якщо доведеться блокувати. Чи означає "синхронний" тут "відсутні видимі програмістом зворотні виклики / стан машини / петлі подій"? Але для вашого прикладу Go я все одно явно чекаю результату від програми, читаючи з каналу, ні? Чим ця менш асинхронність, ніж async / чекає в JS / C # / Python?
амон

1
Я використовую "асинхронний" і "синхронний", щоб обговорити модель програмування, що піддається розробнику, і "блокування" і "неблокування", щоб обговорити блокування потоку ядра, під час якого він не може зробити нічого корисного, навіть якщо є інші обчислення, які потрібно робити, і є запасний логічний процесор, який він може використовувати. Ну, а програма може просто дочекатися результату, не блокуючи нижній потік, але інша гороутина може спілкуватися з нею через канал, якщо бажає. У програмі не потрібно використовувати канал безпосередньо для очікування зчитування неблокуючого сокета.
Луї Джекман

Хм добре, я зараз розумію ваше відмінність. У той час як мене більше хвилює управління потоком даних та контролю між супровідними програмами, ви більше стурбовані тим, що ніколи не блокуєте основну нитку ядра. Я не впевнений, що Go або Haskell мають якусь перевагу перед C ++ або Java в цьому плані, оскільки вони теж можуть запускати фонові нитки, тому для цього просто потрібно більше коду.
Амон

@LouisJackman міг би трохи детальніше розповісти про ваше останнє твердження про неблокування async для системного програмування. Які плюси асинхронного неблокуючого підходу?
sunprophit

@sunprophit Асинхронне неблокування - це лише трансформація компілятора (як правило, асинхронізація / очікування), тоді як синхронне неблокування вимагає підтримки виконання, наприклад, комбінації складних маніпуляцій стеком, вставлення точок виходу на виклики функцій (які можуть стикатися з вбудованим), відстеження " скорочення »(вимагає VM на зразок BEAM) тощо. Як і збирання сміття, він торгує меншою складністю виконання для простоти використання та надійності. Системні мови, такі як C, C ++ та Rust, уникають більших функцій виконання, таких як цільові домени, тому асинхронне неблокування має більше сенсу.
Луї Джекман

2

Якщо я вас правильно читаю, ви запитуєте про модель синхронного програмування, але з високою продуктивністю. Якщо це правильно, тоді це вже є у нас у вигляді зелених ниток або процесів, наприклад, Erlang або Haskell. Так, так, це відмінна ідея, але модернізація існуючих мов не завжди може бути такою гладкою, як хотілося б.


2

Я високо ціную це питання і вважаю, що більшість відповідей є просто захисними від статусного кво. У спектрі мов низького та високого рівня ми деякий час застрягли в колії. Наступний вищий рівень, очевидно, буде мовою, яка менш орієнтована на синтаксис (необхідність явних ключових слів, таких як wait і async) та багато іншого про наміри. (Очевидна заслуга Карла Симоні, але думка про 2019 та майбутнє.)

Якщо я сказав програмісту, напишіть якийсь код, який просто отримує значення з бази даних, ви можете сміливо припустити, що я маю на увазі, "і BTW, не вішайте інтерфейс" та "не вводите інших міркувань, які маскують важко знайти помилки ". Програмісти майбутнього, з наступним поколінням мов та інструментів, безумовно, зможуть записати код, який просто виводить значення в один рядок коду та йде звідти.

Мова найвищого рівня - це розмова англійською мовою, і, спираючись на компетентність виконавця завдань, знати, що ви насправді хочете виконати. (Подумайте про комп’ютер у Star Trek або запитайте щось у Alexa.) Ми далекі від цього, але ближче, і моє сподівання на те, що мова / компілятор може бути більше для створення надійного, наміченого коду, не заходячи так далеко, щоб потребують AI.

З одного боку, існують новіші візуальні мови, як, наприклад, Scratch, які роблять це, і вони не замикаються усіма синтаксичними технічними можливостями. Звичайно, триває багато закулісних робіт, тому програміст не повинен про це турбуватися. Це означає, що я не пишу програмне забезпечення бізнес-класу в Scratch, тому, як і ви, я маю те саме сподівання, що настав час для зрілих мов програмування автоматично керувати синхронною / асинхронною проблемою.


1

Проблема, яку ви описуєте, двояка.

  • Програма, яку ви пишете, повинна вести себе асинхронно в цілому, якщо дивитися зовні .
  • Це повинно сайті виклику не бути видно, чи потенційно функціональний виклик передає контроль чи ні.

Є кілька способів цього досягти, але вони в основному зводяться до цього

  1. маючи кілька ниток (на деякому рівні абстракції)
  2. що мають декілька видів функцій на мовному рівні, всі вони називаються так foo(4, 7, bar, quux).

Для (1) я збираю разом розгалуження та запуск декількох процесів, нерестування декількох потоків ядра та реалізацій зелених ниток, які планують потоки рівня мови та часу виконання на потоках ядра. З точки зору проблеми, вони однакові. У цьому світі жодна функція ніколи не відмовляється і не втрачає управління з точки зору своєї нитки . Сам потік іноді не має керування, а іноді не працює, але ви не відмовляєтесь від контролю над власною потоком у цьому світі. Система, що відповідає цій моделі, може мати або не мати можливість нерестувати нові потоки або приєднуватися до існуючих потоків. Система, що відповідає цій моделі, може мати або не мати можливість дублювати нитку, як у Unix fork.

(2) цікаво. Для здійснення справедливості нам потрібно поговорити про форми введення та усунення.

Я покажу, чому неявне awaitне можна додавати до такої мови, як Javascript, зворотним чином. Основна ідея полягає в тому, що, викриваючи обіцянки користувачеві та розрізняючи синхронний та асинхронний контексти, Javascript просочив деталі реалізації, що запобігає рівномірному поводженню синхронних та асинхронних функцій. Є й той факт, що ти не можешawait можете обіцяти поза тілом функції асинхронізації. Ці варіанти дизайну несумісні з "роблячи асинхронність невидимою для абонента".

Ви можете ввести синхронну функцію за допомогою лямбда та усунути її за допомогою виклику функції.

Введення синхронної функції:

((x) => {return x + x;})

Усунення синхронної функції:

f(4)

((x) => {return x + x;})(4)

Ви можете порівняти це з введенням та усуненням асинхронних функцій.

Введення асинхронної функції

(async (x) => {return x + x;})

Усунення асинхоронної функції (примітка: дійсне лише всередині asyncфункції)

await (async (x) => {return x + x;})(4)

Основна проблема тут полягає в тому, що асинхронна функція - це також синхронна функція, що виробляє об'єкт, що обіцяє .

Ось приклад виклику асинхронної функції синхронно у репліку node.js.

> (async (x) => {return x + x;})(4)
Promise { 8 }

Можна гіпотетично мати мову, навіть динамічно набрану, де різниця між асинхронними та синхронними викликами функцій не видно на сайті виклику і, можливо, не видно на сайті визначення.

Отримавши подібну мову та знизивши її до Javascript, можливо, вам просто доведеться ефективно зробити всі функції асинхронними.


1

За допомогою функцій Go мова та часу запуску мови Go ви можете записати весь код так, ніби це синхронізовано. Якщо операція блокується в одній програмі, виконання продовжується в інших системах. А за допомогою каналів ви можете легко спілкуватися між горами. Це часто простіше, ніж зворотні дзвінки, як у Javascript або асинхронізація / очікування на інших мовах. Див. Https://tour.golang.org/concurrency/1 для деяких прикладів та пояснень.

Крім того, я не маю особистого досвіду з цим, але чую, що Ерланг має подібні засоби.

Так, так, існують мови програмування, як Go і Erlang, які вирішують синхронну / асинхронну проблему, але, на жаль, вони ще не дуже популярні. По мірі зростання популярності цих мов, можливо, засоби, які вони надають, будуть впроваджені й іншими мовами.


Я майже ніколи не використовував мову Go, але здається, ви явно заявляєте go ..., тож це схоже на await ...ні?
Сінн

1
@Cinn Власне, ні. Ви можете надіслати будь-який виклик як горута на власне волокно / зелену нитку go. І майже будь-який виклик, який може блокувати, виконується асинхронно виконуваним часом, який просто перемикається на іншу програму тим часом (кооперативна багатозадачність). Ви чекаєте, чекаючи повідомлення.
Дедуплікатор

2
У той час як Goututines є своєрідною сукупністю, я б не ставив їх у те саме відро, що і async / wait: не кооперативи, а автоматично (і превентивно!) Заплановані зелені нитки. Але це також не робить автоматичного очікування: Еквівалент Go await- читання з каналу <- ch.
амон

@amon Наскільки я знаю, goututine кооперативно плануються на власних потоках (як правило, достатньо, щоб максимально збільшити справжній апаратний паралелізм) під час виконання, і їх попередньо планує ОС.
Дедуплікатор

ОП попросила "мати можливість записувати асинхронний код синхронно". Як ви вже згадували, з goututines та виконуваним часом ви точно можете це зробити. Вам не доведеться турбуватися про деталі нарізки, просто запишіть блокування читання та запису, як би код був синхронним, а інші ваші програми, якщо такі є, продовжуватимуть працювати. Вам також навіть не потрібно "чекати" чи читати з каналу, щоб отримати цю перевагу. Тому я думаю, що Go - це програмна мова, яка найбільше відповідає бажанням ОП.

1

Є ще дуже важливий аспект, який ще не був порушений: перенавчання. Якщо у вас є інший код (наприклад, цикл подій), який працює під час виклику асинхронізації (а якщо ви цього не зробите, навіщо вам навіть потрібна асинхронізація?), Код може вплинути на стан програми. Ви не можете приховати асинхронні дзвінки від абонента, оскільки той, хто телефонує, може залежати від того, які частини програмного стану залишаються незмінними протягом тривалості його виклику функції. Приклад:

function foo( obj ) {
    obj.x = 2;
    bar();
    log( "obj.x equals 2: " + obj.x );
}

Якщо bar()це функція асинхронізації, можливо, obj.xзміни можуть змінитися під час її виконання. Це було б досить несподівано, без будь-якого натяку на те, що бар є асинхронним і такий ефект можливий. Єдиною альтернативою було б запідозрити будь-яку можливу функцію / метод для асинхронізації та повторного отримання та повторної перевірки будь-якого не локального стану після кожного виклику функції. Це схильне до тонких помилок і може бути взагалі неможливим, якщо деякий немісцевий стан буде отриманий за допомогою функцій. Через це програмісту необхідно знати, яка з функцій може змінити стан програми несподіваними способами:

async function foo( obj ) {
    obj.x = 2;
    await bar();
    log( "obj.x equals 2: " + obj.x );
}

Тепер чітко видно, що bar()асинхронна функція, і правильним способом впоратися з нею є повторна перевірка очікуваного значенняobj.x після цього і вирішення будь-яких змін, які могли статися.

Як уже зазначалося в інших відповідях, чисті функціональні мови, такі як Haskell, можуть повністю уникнути цього ефекту, уникаючи потреби в будь-якому спільному / глобальному стані. У мене немає великого досвіду роботи з функціональними мовами, тому я, мабуть, упереджений щодо цього, але не думаю, що відсутність глобальної держави є перевагою при написанні великих додатків.


0

Що стосується Javascript, який ви використовували у своєму запитанні, важливий момент, про який слід пам’ятати: Javascript однопотоковий, і порядок виконання гарантується до тих пір, поки немає асинхронних дзвінків.

Тож якщо у вас є така послідовність, як ваша:

const nbOfUsers = getNbOfUsers();

Вам гарантовано, що поки що нічого не буде виконано. Немає необхідності в замках або в чомусь подібному.

Однак якщо getNbOfUsersасинхронний, то:

const nbOfUsers = await getNbOfUsers();

означає, що поки getNbOfUsers запуску, прибутковість виконання та інший код можуть працювати між ними. Це, в свою чергу, може зажадати певного блокування, залежно від того, що ви робите.

Таким чином, добре розуміти, коли виклик є асинхронним, а коли його немає, так як у деяких випадках вам потрібно буде вжити додаткових запобіжних заходів, які б вам не знадобилися, якщо дзвінок був синхронним.


Ви маєте рацію, мій другий код у питанні недійсний, ніби getNbOfUsers()повертає Обіцянку. Але саме в цьому полягає питання мого питання, чому нам потрібно явно записати його як асинхронний, компілятор міг би виявити його та обробити його автоматично по-іншому.
Сінн

@Cinn це не моя суть. Моя думка полягає в тому, що потік виконання може потрапити до інших частин вашого коду під час виконання асинхронного виклику, тоді як для синхронного виклику це неможливо. Це було б так, як працює кілька потоків, але не знають про це. Це може закінчитися великими проблемами (які зазвичай важко виявити та відтворити).
jcaron

-4

Це доступно в C ++, оскільки std::asyncз C ++ 11.

Функція async шаблону виконує функцію f асинхронно (можливо, в окремому потоці, який може бути частиною пулу потоків) і повертає std :: future, який врешті-решт утримуватиме результат цього виклику функції.

А з C ++ 20 супротинів можна використовувати:


5
Схоже, це не відповідає на питання. За вашим посиланням: "Що дає нам TS Coroutines? Три нові мовні ключові слова: co_await, co_yield та co_return" ... Але питання полягає в тому, для чого нам потрібне await(або co_awaitв даному випадку) ключове слово в першу чергу?
Артуро Торрес Санчес
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.