首頁  >  文章  >  後端開發  >  比較 Python 和 ArkScript 非同步模型

比較 Python 和 ArkScript 非同步模型

WBOY
WBOY原創
2024-09-04 06:51:54660瀏覽

Comparing Python and ArkScript asynchronous models

Python 最近受到了很多關注。計劃於今年 10 月發布的 3.13 版本將開始刪除 GIL 的艱鉅工作。對於想要嘗試(幾乎)無 GIL Python 的好奇用戶來說,預發行版已經發布。

所有這些炒作讓我用我自己的語言ArkScript 進行挖掘,因為我過去也有一個全域VM 鎖(在2020 年的3.0.12 版本中添加,在2022 年的3.1.3 中刪除),以比較事物並迫使我更深入地研究Python GIL 的方式和原因。

定義

  1. 首先,讓我們先定義什麼是 GIL(全域解釋器鎖定):

全域解釋器鎖定(GIL)是電腦語言解釋器中使用的一種機制,用於同步執行緒的執行,以便只有一個本機執行緒(每個進程)可以在同一時間執行基本操作(例如內存分配和引用計數)。時間。

維基百科 — 全域解釋器鎖

  1. 並發是指兩個或多個任務可以在重疊的時間段內啟動、運行和完成,但這並不意味著它們將同時運行。

  2. 並行 是指任務同時運行,例如在多核心處理器上。

有關深入的解釋,請查看此 Stack Overflow 答案。

Python 的 GIL

GIL 可以提高單執行緒程式的速度因為您不必取得和釋放所有資料結構上的鎖定:整個解釋器都被鎖定,因此預設情況下您是安全的。

但是,由於每個解釋器都有一個 GIL,這限制了並行性:您需要在單獨的進程中產生一個全新的解釋器(使用多處理模組而不是線程)才能使用多個核心!這比僅僅產生一個新執行緒的成本更高,因為您現在必須擔心進程間通信,這會增加不可忽略的開銷(有關基準測試,請參閱 GeekPython - Python 3.13 中的 GIL 成為可選)。

它如何影響Python的非同步?

就 Python 而言,這取決於主要實作 CPython 沒有線程安全的記憶體管理。如果沒有 GIL,以下場景將產生競爭條件:

  1. 建立共享變數 count = 5
  2. 線程 1:計數 *= 2
  3. 線程 2:計數 += 1

如果執行緒 1 先運行,計數將為 11(計數 * 2 = 10,然後計數 + 1 = 11)。

如果執行緒 2 先運行,計數將為 12(計數 + 1 = 6,然後計數 * 2 = 12)。

執行順序很重要,但更糟糕的情況可能會發生:如果兩個執行緒同時讀取 count,一個執行緒將會擦除另一個執行緒的結果,而 count 將是 10 或 6!

整體而言,在一般情況下,擁有 GIL 可以讓 (CPython) 實現更輕鬆、更快:

  • 在單執行緒情況下更快(無需為每個操作取得/釋放鎖定)
  • 在 IO 綁定程式的多執行緒情況下更快(因為這些發生在 GIL 之外)
  • 對於在 C 中執行計算密集型工作的 CPU 密集型程式來說,在多執行緒情況下速度更快(因為 GIL 在呼叫 C 程式碼之前被釋放)

它也讓包裝 C 函式庫變得更容易,因為 GIL 保證了執行緒安全。

缺點是您的程式碼是非同步,如並發,但不是並行

[!注意]
Python 3.13 正在刪除 GIL!

PEP 703 新增了建置配置 --disable-gil,以便在安裝 Python 3.13+ 後,您可以從多執行緒程式的效能改進中受益。

Python 非同步/等待模型

在 Python 中,函數必須採用顏色:它們要么是“正常”,要么是“非同步”。這在實踐中意味著什麼?

>>> def foo(call_me):
...     print(call_me())
... 
>>> async def a_bar():
...     return 5
... 
>>> def bar():
...     return 6
... 
>>> foo(a_bar)
25c93a42aa09d6ad19dff8feb1023ce5
c2f32e7e9231c3bf5bf9f218b5147824: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/ except 區塊中先呼叫它來檢查異常,但這很難看)​​。

ArkScript parallelism

In the beginning, ArkScript was using a Global VM Lock (akin to Python's GIL), because the http.arkm module (used to create HTTP servers) was multithreaded and it caused problems with ArkScript's VM by altering its state through modifying variables and calling functions on multiple threads.

Then in 2021, I started working on a new model to handle the VM state so that we could parallelize it easily, and wrote an article about it. It was later implemented by the end of 2021, and the Global VM Lock was removed.

ArkScript async/await

ArkScript does not assign a color to async functions, because they do not exist in the language: you either have a function or a closure, and both can call each other without any additional syntax (a closure is a poor man object, in this language: a function holding a mutable state).

Any function can be made async at the call site (instead of declaration):

(let foo (fun (a b c)
    (+ a b c)))

(print (foo 1 2 3))  # 6

(let future (async foo 1 2 3))
(print future)          # UserType8e288519637d83f9ffb95e89ea0f8cfb
(print (await future))  # 6
(print (await future))  # nil

Using the async builtin, we are spawning a std::future under the hood (leveraging std::async and threads) to run our function given a set of arguments. Then we can call await (another builtin) and get a result whenever we want, which will block the current VM thread until the function returns.

Thus, it is possible to await from any function, and from any thread.

The specificities

All of this is possible because we have a single VM that operates on a state contained inside an Ark::internal::ExecutionContext, which is tied to a single thread. The VM is shared between the threads, not the contexts!

        .---> thread 0, context 0
        |            ^
VM 1d166129c620ba7bd4551066df2b2cf5 thread 1, context 1              

When creating a future by using async, we are:

  1. copying all the arguments to the new context,
  2. creating a brand new stack and scopes,
  3. finally create a separate thread.

This forbids any sort of synchronization between threads since ArkScript does not expose references or any kind of lock that could be shared (this was done for simplicity reasons, as the language aims to be somewhat minimalist but still usable).

However this approach isn't better (nor worse) than Python's, as we create a new thread per call, and the number of threads per CPU is limited, which is a bit costly. Luckily I don't see that as problem to tackle, as one should never create hundreds or thousands of threads simultaneously nor call hundreds or thousands of async Python functions simultaneously: both would result in a huge slow down of your program.

In the first case, this would slowdown your process (even computer) as the OS is juggling to give time to every thread ; in the second case it is Python's scheduler that would have to juggle between all of your coroutines.

[!NOTE]
Out of the box, ArkScript does not provide mechanisms for thread synchronization, but even if we pass a UserType (which is a wrapper on top of type-erased C++ objects) to a function, the underlying object isn't copied.

With some careful coding, one could create a lock using the UserType construct, that would allow synchronization between threads.

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

Conclusion

ArkScript and Python use two very different kinds of async / await: the first one requires the use of async at the call site and spawns a new thread with its own context, while the latter requires the programmer to mark functions as async to be able to use await, and those async functions are coroutines, running in the same thread as the interpreter.

Sources

  1. Stack Exchange — Why was Python written with the GIL?
  2. Python Wiki — GlobalInterpreterLock
  3. stuffwithstuff - What color is your function?

Originally from lexp.lt

以上是比較 Python 和 ArkScript 非同步模型的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn