プログラミング言語が同期/非同期の問題を自動的に管理しないのはなぜですか?


27

私はこれについて多くのリソースを見つけていません:非同期のコードを同期的な方法で書くことができるのか、それとも良い考えかと思っていました。

たとえば、データベースに保存されているユーザーの数を取得するJavaScriptコードを次に示します(非同期操作)。

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

次のようなものを書くことができたらうれしいです:

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

そのため、コンパイラは自動的に応答を待機してから実行しconsole.logます。結果を他の場所で使用する前に、非同期操作が完了するまで常に待機します。コールバックpromise、async / awaitなどを使用することはあまりなく、操作の結果がすぐに利用可能かどうかを心配する必要はありません。

nbOfUserstry / catch、またはSwift言語のようなオプションのようなものを使用して、エラーを管理できます(整数またはエラーを取得しましたか?)。

出来ますか?それはひどいアイデア/ユートピアかもしれません...私は知りません。


58
私はあなたの質問を本当に理解していません。「常に非同期操作を待機する」場合、それは非同期操作ではなく、同期操作です。明確にできますか?たぶんあなたが探している行動のタイプの仕様を教えてください?また、「それについてどう思いますか」は、ソフトウェアエンジニアリングのトピックから外れています。具体的な問題のコンテキストで質問を定式化する必要があります。具体的な問題には、明確で、正統な、客観的に正しい答えが1つあります。
イェルクWミッターク

4
@JörgWMittag暗黙的にawaitsa Task<T>を変換する仮想C#を想像しますT
Caleth

6
あなたが提案することはできません。結果を待つのか、それとも発火して忘れるのかを決めるのはコンパイラ次第ではありません。または、バックグラウンドで実行し、後で待ちます。なぜそんなに自分自身を制限するのですか?
奇妙な

5
はい、それはひどい考えです。代わりにasync/ awaitを使用するだけで、実行の非同期部分が明示的になります。
ベルギ

5
2つのことが同時に起こると言うとき、これらのことが順不同で起こることは問題ないと言っています。コードに、順序の変更がコードの期待を裏切らないことを明確にする方法がない場合、それらを並行させることはできません。
ロブ

回答:


65

Async / awaitは、2つの追加キーワードがありますが、提案するまさにその自動管理です。なぜ重要なのですか?後方互換性は別として?

  • コルーチンを一時停止および再開できる明示的なポイントがない場合、待機可能な値を待機する必要がある場所を検出するための型システムが必要になります。多くのプログラミング言語には、このような型システムがありません。

  • 値の待機を明示的にすることにより、待機可能な値をファーストクラスのオブジェクトとして渡すことができます:約束。これは、高次のコードを書くときに非常に便利です。

  • 非同期コードは、言語の例外の有無と同様に、言語の実行モデルに非常に深い影響を及ぼします。特に、非同期関数は非同期関数によってのみ待機できます。これは、すべての呼び出し関数に影響します!しかし、この依存関係チェーンの終わりに非同期から非同期に関数を変更するとどうなりますか?これは後方互換性のない変更になります…すべての関数が非同期で、すべての関数呼び出しがデフォルトで待機されている場合を除きます。

    また、パフォーマンスへの影響が非常に悪いため、これは非常に望ましくありません。単純に安い値を返すことはできません。すべての関数呼び出しは、はるかに高価になります。

非同期は素晴らしいですが、ある種の暗黙的な非同期は実際には機能しません。

Haskellのような純粋な関数型言語には、実行順序がほとんど指定されておらず、観察できないため、少しエスケープハッチがあります。または、異なる言い方をします:操作の特定の順序は明示的にエンコードする必要があります。実際のプログラム、特に非同期コードが非常に適しているI / Oが重いプログラムでは、これはかなり面倒です。


2
型システムは必ずしも必要ではありません。ECMAScript、Smalltalk、Self、Newspeak、Io、Ioke、Sephなどの透明な未来は、tyoeシステムまたは言語サポートなしで簡単に実装できます。Smalltalkとその子孫では、オブジェクトはIDを透過的に変更できます。ECMAScriptでは、オブジェクトは形状を透過的に変更できます。Futuresを透過的にするために必要なのはこれだけです。非同期の言語サポートは必要ありません。
イェルクWミッターク

6
@JörgWMittagあなたが言っていることとそれがどのように機能するか理解していますが、型システムのない透明な未来は、ファーストクラスの未来を同時に持つことをかなり難しくしていますか?メッセージを未来に送るか未来の価値に送るかを選択する方法が必要です。できればsomeValue ifItIsAFuture [self| self messageIWantToSend]、汎用コードと統合するのが難しいからです。
アモン

8
@amon「約束と約束はモナドであるため、非同期コードを書くことができます。」ここでは、モナドは実際には必要ありません。サンクは基本的に単なる約束です。Haskellのほとんどすべての値はボックス化されているため、Haskellのほとんどすべての値はすでに約束されています。だからこそ、あなたはpar純粋なHaskellコードのどこにでも放り投げて、無料で並列処理を行うことができます。
ダースフェネック

2
Async / awaitは、継続モナドを思い出させます。

3
実際、例外とasync / awaitはどちらも代数効果のインスタンスです。
アレックスラインキング

21

不足しているのは、非同期操作の目的です:待機時間を利用することができます!

暗黙的かつ即座に応答を待機することにより、サーバーからリソースを要求するなどの非同期操作を同期操作に変更すると、スレッドは待機時間で他に何もできなくなります。サーバーが応答するのに10ミリ秒かかる場合、約3,000万CPUサイクルが無駄になります。応答の待ち時間は、要求の実行時間になります。

プログラマーが非同期操作を発明した唯一の理由は、本質的に長時間実行されるタスクの遅延を他の有用な計算の背後に隠すことです。有用な作業で待ち時間を埋めることができれば、それはCPU時間の節約になります。できない場合は、操作が非同期であることによって何も失われません。

したがって、言語が提供する非同期操作を採用することをお勧めします。時間を節約するためにあります。


私は、操作がブロックされない関数型言語を考えていたので、たとえそれが同期構文を持っているとしても、長時間の計算はスレッドをブロックしません
Cinn

6
@Cinn質問にはそれが見つかりませんでした。質問の例はJavascriptで、この機能はありません。ただし、一般的に、コンパイラが説明するように並列化の有意義な機会を見つけるのはかなり困難です。そのような機能を有意に活用するには、プログラマが長い待ち時間の呼び出しの直後にを置くを明示的に考える必要があります。プログラマーのこの要件を回避するためにランタイムを十分にスマートにすると、ランタイムは関数呼び出し間で積極的に並列化する必要があるため、パフォーマンスの節約を食いつぶす可能性があります。
cmaster

2
すべてのコンピューターは同じ速度で待機します。
ボブジャービス-復帰モニカ

2
@BobJarvisはい。しかし、彼らは待ち時間にどれだけの仕事をすることできたかで異なります...
cmaster

13

ある人はそうします。

彼らは主流ではありません(まだ)表現力豊かな/など 既存の非同期機能は主に既存の言語にボルトで固定されているため、少し異なる設計アプローチが必要です。

とはいえ、どこでも行うのは明らかに良いアイデアではありません。一般的な失敗は、非同期呼び出しをループで実行し、その実行を効果的にシリアル化することです。非同期呼び出しを暗黙的にすると、その種のエラーがあいまいになる場合があります。また、からTask<T>(またはあなたの言語の同等物)からの暗黙的な強制をサポートTする場合、プログラマーがどちらを本当に望んだかが不明な場合、タイプチェッカーとエラー報告に多少の複雑さ/コストを追加できます。

しかし、それらは克服できない問題ではありません。その動作をサポートしたい場合、ほぼ確実にできますが、トレードオフがあります。


1
非同期関数ですべてをラップすることが考えられると思います、同期タスクはただちに解決し、すべての種類を処理します(編集:@amonはなぜそれが悪い考えであるかを説明しました...)
Cinn

8
Some do」のいくつかの例を挙げてください。
ベルギ

2
非同期プログラミングは決して新しいものではなく、今日では人々がより頻繁に対処しなければならないということです。
キュービック

1
@Cubic-私の知る限り、これは言語機能です。それがちょうど(厄介な)ユーザーランド機能になる前。
Telastyn

12

これを行う言語があります。しかし、既存の言語機能で簡単に実現できるため、実際にはそれほど必要ありません。

限り、あなたは持っているように、いくつかの非同期性を表現する方法を、あなたが実装することができ先物約束を純粋にライブラリ機能として、あなたは特別な言語機能を必要としません。そして、あなたが透明なプロキシを表現するものを持っている限り、2つの機能を組み合わせることができ、透明な未来があります。

たとえば、Smalltalkとその子孫では、オブジェクトはそのアイデンティティを変更でき、文字通り別のオブジェクトに「なる」ことができます(実際、これを行うメソッドはと呼ばれますObject>>become:)。

を返す長時間実行の計算を想像してくださいFuture<Int>。異なる実装を除いて、これにFuture<Int>はと同じメソッドがすべてありIntます。Future<Int>+メソッドは別の数値を追加して結果を返すのではなくFuture<Int>、計算をラップする新しい値を返します。などなど。常識的に返すことによって実装することができない方法Future<Int>、代わりに自動的になりますawaitし、結果、およびコールself become: result.現在実行中のオブジェクトになりますこれは、(selfすなわちFuture<Int>)文字通りになるresultように使用されるオブジェクト参照の上、今からつまり、オブジェクトFuture<Int>であります今ではIntどこでも、クライアントに対して完全に透過的です。

非同期関連の特別な言語機能は必要ありません。


わかりましたが、両方とも共通のインターフェイスを共有し、そのインターフェイスの機能を使用するFuture<T>と問題が発生Tします。become結果として、機能を使用するべきかどうか。私は等価演算子や文字列へのデバッグ表現のようなものを考えています。
アモン

私はそれが機能を追加しないことを理解しています。問題は、すぐに解決する計算と長時間実行される計算を書くためのさまざまな構文があり、その後、他の目的で同じ方法で結果を使用することです。両方を透過的に処理する構文があり、読みやすくなっているので、プログラマは処理する必要がないのではないかと思っていました。やって同じようにa + baとbがすぐ以降使用可能な場合、両方の整数、ない事項を、私たちは、ライトa + b(行うことが可能となるInt + Future<Int>
CINN

@Cinn:はい、Transparent Futuresでそれを行うことができます。それを行うのに特別な言語機能は必要ありません。Smalltalk、Self、Newspeak、Us、Korz、Io、Ioke、Seph、ECMAScriptなどの既存の機能を使用して実装できます。
イェルクWミッターク

3
@amon:Transparent Futuresのアイデアは、それが未来だとは知らないということです。あなたの視点からは、間に共通のインタフェースが存在しないFuture<T>Tあなたの視点から、理由はありませんがあるFuture<T>だけで、T。今、もちろん、これを効率的にする方法に関するエンジニアリング上の多くの課題があります。どの操作をブロックするか、非ブロックするかなどです。しかし、それは言語として、またはライブラリ機能としてそれを行うかどうかとは本当に独立しています。透明性は質問のOPで規定された要件でしたが、私はそれが難しく、意味をなさないかもしれないと主張しません。
イェルクWミッターク

3
@Jörgそのモデルで実際にコードがいつ実行されるかを知る方法がないため、関数型言語以外では問題があるようです。Haskellでは一般的にうまくいきますが、より手続き型の言語ではこれがどのように機能するかわかりません(そしてHaskellでも、パフォーマンスを気にする場合、実行を強制し、基礎となるモデルを理解する必要があります)。それにもかかわらず、興味深いアイデア。
Voo

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();
    }
    

    3か月後、プレーヤーattacker.addInventoryItemsは実行中に正確に殺されてログオフするvictim.removeInventoryItemsと失敗し、アイテムを保持でき、攻撃者もアイテムのコピーを取得できることを発見します。彼はこれを数回行い、薄い空気から100万トンの金を作り出し、ゲームの経済を破壊しました。

    また、ゲームが犠牲者にメッセージを送信している間に攻撃者がログアウトでき、頭の上に「殺人者」タグが表示されないため、次の犠牲者は逃げられません。

  2. コードはいつでも中断できるため、データ構造を操作するときはどこでもロックを使用する必要があります。ゲームで明らかな結果をもたらす上記の例を挙げましたが、より微妙な場合があります。リンクリストの先頭にアイテムを追加することを検討してください。

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

    スレッドは、I / Oを実行しているときにのみ一時停止でき、どの時点でも一時停止できないと言っても問題ではありません。しかし、ロギングなどのI / O操作がある状況を想像できると確信しています。

    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を介して非同期性を使用して、ブロックオペレーションをイベントループからディスパッチできるようにし、JavaのNettyはコールバックまたはCompletableFuturesを介して非同期性を使用して同様のことを行います。

ただし、ノンブロッキングコードは非同期性を必要としません。それはあなたのプログラミング言語とランタイムがあなたのためにどれだけ喜んでやるかに依存します。

Go、Erlang、Haskell / GHCがこれを処理します。何かを書いてvar response = http.get('example.com/test')、応答を待っている間に、舞台裏でカーネルスレッドを解放することができます。これは、ゴルーチン、Erlangプロセス、またはforkIOブロック時にバックグラウンドでカーネルスレッドを放すことによって行われ、応答を待機しながら他のことを実行できるようにします。

言語が実際に非同期性を処理できないことは事実ですが、一部の抽象化では、無制限の継続や非対称コルーチンなど、他の抽象化よりもさらに先に進むことができます。ただし、非同期コードの主な原因であるシステムコールのブロックは、絶対に開発者から引き離すことできます。

Node.jsとJavaは非同期のノンブロッキングコードをサポートしていますが、GoとErlangは同期のノンブロッキングコードをサポートしています。どちらも異なるトレードオフを持つ有効なアプローチです。

私のかなり主観的な議論は、開発者に代わってノンブロッキングを管理するランタイムに反対する人々は、初期の半ばにガベージコレクションに反対する人々と似ているということです。はい、コストがかかります(この場合は主にメモリが増えます)が、開発とデバッグが容易になり、コードベースがより堅牢になります。

個人的には、非同期の非ブロッキングコードを将来のシステムプログラミング用に予約し、より最新のテクノロジースタックをアプリケーション開発のために同期の非ブロッキングランタイムに移行する必要があると主張します。


1
これは本当に興味深い答えでした!しかし、「同期」と「非同期」のノンブロッキングコードの違いを理解しているかどうかはわかりません。私にとって、同期非ブロックコードwaitpid(..., WNOHANG)とは、ブロックする必要がある場合に失敗するようなC関数のようなものを意味します。または、ここでの「同期」とは、「プログラマが目に見えるコールバック/ステートマシン/イベントループがない」ことを意味しますか?しかし、Goの例では、チャンネルから読み取ることでゴルーチンの結果を明示的に待機する必要がありますか?これは、JS / C#/ Pythonのasync / awaitよりも非同期性が低いのですか?
アモン

1
「非同期」と「同期」を使用して、開発者に公開されているプログラミングモデルについて説明し、「ブロック」と「非ブロック」を使用して、カーネルスレッドのブロックについて説明します。実行する必要がある他の計算、および使用できる予備の論理プロセッサがあります。ゴルーチンは、基礎となるスレッドをブロックせずに結果を待つことができますが、別のゴルーチンは必要に応じてチャネルを介して結果と通信できます。ゴルーチンは、非ブロッキングソケットの読み取りを待つためにチャネルを直接使用する必要はありません。
ルイジャックマン

うーん、あなたの区別がわかりました。コルーチン間のデータフローと制御フローの管理がより重要であるのに対し、メインカーネルスレッドをブロックしないことをより懸念しています。GoやHaskellがこの点でC ++やJavaを上回る利点があるかどうかはわかりません。バックグラウンドスレッドを開始できるので、少しコードを追加するだけです。
アモン

@LouisJackmanは、システムプログラミングの非同期ノンブロッキングに関する最後のステートメントについて少し詳しく説明できます。非同期ノンブロッキングアプローチの長所は何ですか?
sunprophit

@sunprophit非同期非ブロックは単なるコンパイラ変換(通常はasync / await)ですが、同期非ブロックには、複雑なスタック操作、関数呼び出しでの降伏点の挿入(インライン化と衝突する可能性あり)、 (BEAMのようなVMが必要)など。ガベージコレクションと同様に、使いやすさと堅牢性のために、実行時の複雑さを軽減します。C、C ++、Rustなどのシステム言語は、ターゲットドメインのために、このような大きなランタイム機能を避けているため、非同期の非ブロッキングがより理にかなっています。
ルイジャックマン

2

私があなたを正しく読んでいるなら、あなたは同期プログラミングモデルを求めていますが、高性能の実装を求めています。それが正しければ、ErlangやHaskellなどのグリーンスレッドまたはプロセスの形ですでに利用可能です。はい、それは素晴らしいアイデアですが、既存の言語へのレトロフィットはあなたが望むように常にスムーズであるとは限りません。


2

私は質問に感謝し、答えの大部分は現状を単に擁護するものであると思います。低レベルから高レベルの言語のスペクトルでは、私たちはしばらくの間わだかまりに陥っていました。次のより高いレベルは、明らかに構文(awaitやasyncのような明示的なキーワードの必要性)にあまり焦点を合わせていない言語であり、意図についてはもっと重要です。(Charles Simonyiの名誉は明らかですが、2019年と未来を考えています。)

プログラマーにデータベースから値を取得するだけのコードを書くように言ったら、「ところで、UIをハングさせないで」、「バグを見つけにくいマスクをする他の考慮事項を導入しないで」 「。次世代の言語とツールを備えた未来のプログラマーは、1行のコードで値を取得してそこから進むコードを確実に書くことができます。

最高レベルの言語は英語を話すことであり、タスク実行者の能力に依存して、あなたが本当にやりたいことを知ることです。(スタートレックでコンピューターを考えるか、Alexaに何かを尋ねる。)私たちはそれからは程遠いが、少し近づいて、私の期待は言語/コンパイラーが、 AIが必要です。

一方では、Scratchのような新しい視覚言語があります。これは、これを実行し、すべての構文上の技術にとらわれません。確かに、多くの舞台裏の作業が行われているので、プログラマーは心配する必要はありません。そうは言っても、私はスクラッチでビジネスクラスのソフトウェアを書いているわけではないので、あなたのように、成熟したプログラミング言語が同期/非同期の問題を自動的に管理する時が来ると同じ期待を持っています。


1

説明している問題は2つあります。

  • あなたが書いているプログラムは外部から見たとき全体として非同期に振る舞うべきです。
  • 関数呼び出しが制御を放棄する可能性があるかどうかにかかわら、呼び出しサイトで表示されるべきでありません。

これを実現するにはいくつかの方法がありますが、基本的には

  1. 複数のスレッドを持つ(ある抽象化レベルで)
  2. 言語レベルで複数の種類の機能を持ち、それらはすべてこのように呼び出されfoo(4, 7, bar, quux)ます。

(1)では、複数のプロセスをフォークして実行し、複数のカーネルスレッドを生成し、言語ランタイムレベルのスレッドをカーネルスレッドにスケジュールするグリーンスレッドの実装をまとめています。問題の観点からは、それらは同じです。この世界では、スレッドの観点から制御を放棄したり、制御を失ったりする機能はありませんスレッド自体は時々コントロールを持っていないと、時々実行されていないが、あなたはこの世界で独自のスレッドの制御をあきらめないでください。このモデルに適合するシステムは、新しいスレッドを生成したり、既存のスレッドに参加したりする機能を持つ場合と持たない場合があります。このモデルに適合するシステムには、Unixのようなスレッドを複製する機能がある場合とない場合がありますfork

(2)興味深いです。それを正すために、私たちは導入と排除の形について話す必要があります。

awaitJavascriptのような言語に暗黙的に下位互換性のある方法で追加できない理由を示します。基本的な考え方は、約束をユーザーに公開し、同期コンテキストと非同期コンテキストを区別することにより、同期と非同期の関数を均一に処理できない実装の詳細を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)

ここでの基本的な問題は、非同期関数がpromiseオブジェクトを生成する同期関数でもあるということです。

node.js replで非同期関数を同期的に呼び出す例を次に示します。

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

仮に、動的に型付けされた言語であっても、非同期関数呼び出しと同期関数呼び出しの違いが呼び出しサイトで見えず、場合によっては定義サイトで見えない言語を持つことができます。

そのような言語をJavascriptに変換することは可能ですが、すべての機能を効果的に非同期にする必要があります。


1

Go言語のゴルーチンとGo言語のランタイムを使用すると、すべてのコードを同期しているかのように記述できます。1つのゴルーチンで操作がブロックされると、他のゴルーチンで実行が続行されます。また、チャネルを使用すると、ゴルーチン間で簡単に通信できます。これは、多くの場合、Javascriptや他の言語のasync / awaitのようなコールバックよりも簡単です。いくつかの例と説明については、https://tour.golang.org/concurrency/1を参照してください

さらに、個人的な経験はありませんが、Erlangには同様の機能があると聞きました。

そのため、はい、GoやErlangのようなプログラミング言語があります。これらは同期/非同期の問題を解決しますが、残念ながらまだあまり普及していません。これらの言語の人気が高まるにつれて、おそらくそれらが提供する機能は他の言語でも実装されるでしょう。


私はGo言語をほとんど使用しませんでしたがgo ...、明示的に宣言しているようですので、await ...no と似ていますか?
シン

1
@Cinn実はいや。を使用して、独自のファイバー/グリーンスレッドにゴルーチンとして呼び出しを行うことができますgo。そして、ブロックする可能性のあるほぼすべての呼び出しは、ランタイムによって非同期に実行され、その間に別のゴルーチンに切り替えます(協調マルチタスク)。あなたはメッセージを待って待っています。
デデュプリケーター

2
ゴルーチンは一種の並行性ですが、非同期/待機と同じバケットには入れません。協調型コルーチンではなく、自動的に(そして予防的に!)スケジュールされたグリーンスレッドです。しかし、これは自動的に待つことにもなりません:Go awaitは、チャンネルからの読み取りと同等<- chです。
アモン

@amon私の知る限り、ゴルーチンはランタイムによってネイティブスレッド上で協調的にスケジュールされ(通常、真のハードウェア並列処理を最大限にするのに十分なだけ)、OSによって先制的にスケジュールされます。
デデュプリケーター

OPは、「非同期コードを同期方式で記述できるようにする」よう求めました。既に述べたように、ゴルーチンとgoランタイムを使用すると、まさにそれを行うことができます。スレッド化の詳細を心配する必要はありません。コードが同期的であるかのように、読み取りと書き込みをブロックするだけで、他のゴルーチンがあれば実行され続けます。また、この利点を得るために「待つ」またはチャネルから読み取る必要もありません。したがって、GoはOPの要望に最も近いプログラミング言語であると思います。

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、実行中に実行が譲歩し、他のコードがその間に実行される可能性があることを意味します。これは、あなたが何をしているのかにもよりますが、ロックを行う必要があります。

そのため、状況によっては、呼び出しが同期である場合に必要としない追加の予防措置を講じる必要があるため、呼び出しが非同期である場合とそうでない場合に注意することをお勧めします。


あなたは正しい、質問の私の2番目のコードgetNbOfUsers()は、Promiseを返すかのように無効です。しかし、それがまさに私の質問のポイントです。なぜ非同期として明示的に記述する必要があるのか​​、コンパイラはそれを検出し、別の方法で自動的に処理できます。
シン

@Cinnそれは私のポイントではありません。私のポイントは、非同期呼び出しの実行中に実行フローがコードの他の部分に到達する可能性がある一方で、同期呼び出しは不可能だということです。複数のスレッドを実行しているが、それを認識していないようなものです。これにより、大きな問題が発生する可能性があります(通常、検出と再現が困難です)。
ジャカロン

-4

これはstd::async、C ++ 11以降のC ++で利用可能です。

テンプレート関数asyncは、関数fを非同期で(スレッドプールの一部である可能性のある別のスレッドで)実行し、その関数呼び出しの結果を最終的に保持するstd :: futureを返します。

また、C ++ 20コルーチンを使用できます。


5
これは質問に答えていないようです。あなたのリンクによると:「コルーチンTSは何を提供しますか?3つの新しい言語キーワード:co_await、co_yield、co_return」...しかし、そもそもawait(またはco_awaitこの場合)キーワードが必要なのはなぜですか?
アルトゥーロトーレスサンチェス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.