Python は最近非常に注目を集めています。今年 10 月に予定されている 3.13 リリースでは、GIL を削除するという大規模な作業が開始されます。 (ほぼ) GIL なしの Python を試してみたい好奇心旺盛なユーザー向けに、プレリリースがすでにリリースされています。
この誇大宣伝のせいで、私も自分の言語である ArkScript を掘り下げるようになりました。私も過去に Global VM Lock を持っていたからです (2020 年のバージョン 3.0.12 で追加、2022 年の 3.1.3 で削除)。物事を比較して、Python GIL の使用方法と理由をさらに深く掘り下げるように強制してください。
グローバル インタプリタ ロック (GIL) は、コンピュータ言語のインタプリタでスレッドの実行を同期するために使用されるメカニズムで、一度に 1 つのネイティブ スレッド (プロセスごと) だけが基本操作 (メモリ割り当てや参照カウントなど) を実行できるようにします。時間。
Wikipedia — グローバルインタープリターロック
同時実行とは、2 つ以上のタスクが重複する期間で開始、実行、完了できることを指しますが、両方が同時に実行されることを意味するわけではありません。
並列処理 は、マルチコア プロセッサなどでタスクが文字通り同時に実行される場合です。
詳細な説明については、このスタック オーバーフローの回答を確認してください。
GIL は シングルスレッド プログラムの速度を向上させることができます。すべてのデータ構造のロックを取得および解放する必要がないためです。インタプリタ全体がロックされているため、デフォルトで安全です。
ただし、インタープリターごとに 1 つの GIL があるため、並列処理が制限されます。複数のコアを使用するには、別のプロセス (スレッドの代わりにマルチプロセッシング モジュールを使用) でまったく新しいインタープリターを生成する必要があります。これには、プロセス間通信について考慮する必要があり、無視できないオーバーヘッドが追加されるため、新しいスレッドを生成するよりもコストが高くなります (ベンチマークについては、「GeekPython — GIL become Optional in Python 3.13」を参照してください)。
Python の場合、主な実装である CPython にスレッドセーフなメモリ管理がないことが原因です。 GIL がない場合、次のシナリオでは競合状態が生成されます:
スレッド 1 が最初に実行される場合、カウントは 11 になります (カウント * 2 = 10、カウント 1 = 11)。
スレッド 2 が最初に実行される場合、カウントは 12 になります (カウント 1 = 6、カウント * 2 = 12)。
実行順序は重要ですが、さらに悪いことが起こる可能性があります。両方のスレッドが同時に count を読み取った場合、一方が他方の結果を消去し、count は 10 または 6 になります!
全体として、GIL を使用すると、一般的に (CPython) 実装が簡単かつ高速になります:
また、GIL のおかげでスレッドセーフが保証されるため、C ライブラリのラップも簡単になります。
欠点は、コードが 同時 のように 非同期 ですが、並列ではないことです。
[!注記]
Python 3.13 では GIL が削除されます!PEP 703 では、ビルド構成 --disable-gil が追加されたため、Python 3.13 をインストールすると、マルチスレッド プログラムのパフォーマンス向上の恩恵を受けることができます。
Python では、関数には色が必要です。関数は「通常」または「非同期」のいずれかです。これは実際には何を意味しますか?
>>> def foo(call_me): ... print(call_me()) ... >>> async def a_bar(): ... return 5 ... >>> def bar(): ... return 6 ... >>> foo(a_bar):2: RuntimeWarning: coroutine 'a_bar' was never awaited RuntimeWarning: Enable tracemalloc to get the object allocation traceback >>> foo(bar) 6
非同期関数はすぐに値を返すのではなく、コルーチンを呼び出すため、呼び出している関数が非同期コールバックを受け取るように設計されていない限り、どこでもコールバックとして使用することはできません。
非同期関数を呼び出すために必要な await キーワードを使用するには、「通常の」関数を非同期にする必要があるため、関数の階層が得られます。
can call normal -----------> normal can call async - -----------> normal | .-----------> async
呼び出し元を信頼すること以外に、コールバックが非同期かどうかを知る方法はありません (例外をチェックするために try/excel ブロック内で最初にコールバックを呼び出そうとする場合を除きますが、それは見苦しいです)。
当初、ArkScript はグローバル VM ロック (Python の GIL に似た) を使用していました。これは、http.arkm モジュール (HTTP サーバーの作成に使用される) がマルチスレッドであり、変数の変更によって状態が変更されることで ArkScript の VM に問題が発生したためです。複数のスレッドで関数を呼び出します。
そして 2021 年に、VM の状態を簡単に並列化できるように処理するための新しいモデルの開発に着手し、それに関する記事を書きました。その後、2021 年末までに実装され、グローバル VM ロックは削除されました。
ArkScript は非同期関数に色を割り当てません。非同期関数は言語に存在しないためです。関数またはクロージャーがあり、両方とも追加の構文なしで相互に呼び出すことができます (クロージャーは貧乏人オブジェクトです。この言語では: 可変状態を保持する関数).
任意の関数を 呼び出しサイト (宣言の代わりに) で非同期にすることができます:
(let foo (fun (a b c) ( a b c))) (print (foo 1 2 3)) # 6 (let future (async foo 1 2 3)) (print future) # UserType (print (await future)) # 6 (print (await future)) # nil
async 組み込みを使用して、内部で std::future を生成し (std::async とスレッドを利用して)、一連の引数を指定して関数を実行します。その後、await (別の組み込み) を呼び出して、必要なときにいつでも結果を取得できます。これにより、関数が返されるまで現在の VM スレッドがブロックされます。
したがって、任意の関数および任意のスレッドから待機することが可能です。
これはすべて、単一のスレッドに関連付けられた Ark::internal::ExecutionContext 内に含まれる状態で動作する単一の VM があるため可能です。 VM はコンテキストではなくスレッド間で共有されます!
.---> thread 0, context 0 | ^ VM thread 1, context 1
非同期を使用して フューチャー を作成すると、次のようになります:
これにより、ArkScript は参照や共有できるあらゆる種類のロックを公開しないため、スレッド間のあらゆる種類の同期が禁止されます (これは、言語がある程度ミニマリストでありながらも使用可能であることを目指しているため、単純化するために行われました)。
ただし、このアプローチは、呼び出しごとに新しいスレッドを作成し、CPU あたりのスレッド数が制限されているため、少しコストがかかるため、Python より優れているわけでも劣っているわけでもありません。幸いなことに、私はそれが取り組むべき問題とは考えていません。なぜなら、数百または数千のスレッドを同時に作成したり、数百または数千の非同期 Python 関数を同時に呼び出したりしてはいけないからです。どちらも、プログラムの大幅な速度低下につながります。
最初のケースでは、OS がすべてのスレッドに時間を与えるためにジャグリングするため、プロセス (コンピュータであっても) が遅くなります。 2 番目のケースでは、Python のスケジューラーがすべてのコルーチンの間でやりくりする必要があります。
[!注記]
ArkScript は、そのままではスレッド同期のメカニズムを提供しませんが、UserType (type-erased C オブジェクトの上にあるラッパー) を関数に渡しても、基になるオブジェクトは同期されません。コピーされていません。
注意深くコーディングすると、UserType 構造を使用してロックを作成でき、スレッド間の同期が可能になります。(let lock (module:createLock)) (let foo (fun (lock i) { (lock true) (print (str:format "hello {}" i)) (lock false) })) (async foo lock 1) (async foo lock 2)
ArkScript と Python は 2 つのまったく異なる種類の async / await を使用します。前者は呼び出しサイトで async を使用する必要があり、独自のコンテキストを持つ新しいスレッドを生成します。一方、後者はプログラマが関数を非同期としてマークする必要があります。 await を使用でき、それらの非同期関数はコルーチンであり、インタープリターと同じスレッドで実行されます。
元のソースは lexp.lt
免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。
Copyright© 2022 湘ICP备2022001581号-3