Re: [問題] 請問 Coroutine & 一般 callback 合作的問題

作者: HuangJC (吹笛牧童)   2023-02-08 14:18:15
※ 引述《zerof (貓橘毛發呆雕像)》之銘言:
: : 我也可以用 multi-process 而不是 thread 來解啊
: : 解法不只一種,而且我不認為問題出在 GIL
: : 畢竟我的 thread 有三十多個,我也真嫌多了
: : 用了 Coroutine 就合併回一個,但卻是三十多個 task
: : 我覺得這也蠻好的
: : Coroutine 既然是個潮流就來了解一下,有別的解法就先放一邊吧..
: : 真要 C 我何不回 C 的世界,寫純 C..
: 你要不要看看 Chrome 開起來的時候平常是起幾個 Threads ?
在你這篇文前我就想過這個問題了
可是這個比較並不公平
你基於 chrome 跑起來流暢,偷渡了 python 即使只寫 single thread
執行速度都輸給 c 的事實
一個是 compile 語言,一個是 script
是否 chrome 也用 python 寫,但用上 pypy 取消 gil 的影響再來比?
還有,我的 python 程式在 Mac 上開發,也感覺很流暢啊
是傳到 RPI 上才跑得有點慢的
chrome 有很快嗎?瀏覽器在 RPI 上,感覺也慢不少啊
電腦等級不一樣也擺在一起比?
有一件事我沒在前面的文章中提及,就是 multi-thread 在 debug 時給我的困擾
我用中斷點暫停了一個 thread, 但其他 29 個 thread 還在跑
這導致大家共用的變數飛快的改變著,我很難單步執行
(但的確又有一些 thread 在背景還能跑也給了我方便)
而 Coroutine 它們事實上是同一個 thread
這件事讓我可以在中斷一個 task 時,其他 task 都停下來
這是我要的;之前沒 Coroutine 我也漸漸在安排這種架構
比如我有十個 sensor, 本來各跑一個 thread,後來串起來在同一個 thread 跑
比如本來一秒讀一次,我就一秒啟動一個新的 timer thread
結果我中斷在某個 sensor read 時,
還每一秒都有一個新的 read request 闖進來,不堪其擾了
所以雖然讀 sensor 是飛快的事,一秒內一定能讀完,可以一秒啟動一個 timer thread
我還是只啟動一個 thread 並且寫成 while loop
這樣只要讀取沒完成,就不會有新的 read request
可以說 free run 並沒有問題,是 debug 時出一堆問題
就我看目前我的程式都不要改,卡的情況老闆也能接受
不接受的是我,因為只要 debug, 程式就卡得不能動
當然短期內我不用單步執行,狂加 log 也解了不少問題
: Coroutine 是潮流? 你不是寫 C 嗎怎麼沒用過 libtask ?
真沒用過
: 這也難怪前面被嗆先回去讀 OS , Preemptive/Cooperative multitasking 是不是
: 都沒聽過 ?
這就有聽過了,寫過 windows sdk, message loop 就類似協程的一種
guizero,或者說一堆 GUI framework, 都用 callback 或 event driven 運作
就算這名詞沒聽過,也許我已經在寫這種程式了
所以其實我本來想自己打造 Coroutine 的
就我看,task 其實都是在同一個 thread 跑
但是有一個 framework 巧妙的把它轉換成 message loop 的型式
只要目前執行中的 task 阻塞,就跳轉另一個 task 跑
: 如果問題不是出在 GIL ,那問題是出在你的 code 架構不對?
如果我對 Coroutine 的理解沒錯,也就是它只是幫我實現我想要的架構
我完全可以不用 Coroutine,但又只用一個 thread 寫出來
所以,是的:我的 code 架構不對
我常說的一件事:如果寫一個射擊電玩,畫面上有五十架敵機
哪有必要用 50 個 thread 去寫?當然一個 thread 運作五十架敵機沒有問題
但因為我在安排架構上出了問題,所以才改用 thread 寫
在 thread 內我可以專注在運算一架飛機
寫著寫著想要 sleep 就sleep, 不用擔心其他飛機被暫停了;反正它們在其他 thread
看在事實上只有一顆 CPU,只有一個核心的份上
我認為所有的 multi-thread 其實都可以寫成 single thread
用上 multi-thread 不就是因為思維可以清晰,有底層硬體支援
所以我的邏輯概念可以拋掉一些事由 OS 負責嗎?
Coroutine 也給我這種感覺
現在我可以想 sleep 就 sleep, 控制權會交給其他 task
而我的程式語法還是單純
我本來要自己硬幹的,若我幹出來了,我就會說那個架構好,而現在這個架構差
方法就是打造自己的 message loop,把一份工作切成許多程序薄片
每一個薄片做完就切換到另一個薄片,每個薄片間沒有 multi-thread 會發生的交鏈
這樣就算沒 GIL,我也是自己手工打造了一樣的副作用
所以我不會抱怨 GIL
: 我確實是蠻好奇的啦,如果你可以斷定不是出在 GIL ,難道 PyQT 的 QThread 是寫
: 來好看的?
話不能這麼說,常說的一句話:問題有很多解法
別人依賴我不依賴啊..
PyQT 把這架構,打造給依賴它的人
(區塊變色好難,好像只變到一行?)
: 我想你從頭到尾沒有搞清楚 GIL 所以根本不明白 GUI 為什麼會 freezing
: threading 本身就是 preemptive 的, GIL threads 在 context switching 的時候
: 會有 Locking 的問題,反過來說, GUI 要執行的 main thread 無法保證總是搶得到
: GIL ,而產生 freeze 的現象;當你 threads 開得多,裡面又都是跑 python code
: 的時候就會非常的明顯。
: Python 連 serial/smbus 的 libraries 都是 blocking I/O ,最底層摸到 sockets
: 的時候的確是會 release GIL ,但回過頭來就還是 preemptive 的本質:你沒辦法保
: 證 GUI 用的 thread 一定會搶到 GIL 。
這些我知道
: 這問題換到用 asyncio 並不會被解決,本質上的差別。 asycnio 底層用的 sockets
: 是 non-blocking 的,你用同樣的 libraries 來跑,一樣會遇到 GIL 的問題,而且
: 更嚴重。(GUI 一個 loop, asyncio 一個 loop)
用 29 個 working thread 和一個 ui/main thread 來說
我目前的架構是 free run 時,每個 thread 佔用三十分之一的 process 效率
UI 必需和大家共享,那也才三十分之一, 3%
但我不很在乎我的 working thread, 願意合併成一個 thread,內含 29 個 task
於是 main thread 變成 二分之一,50% 的效率
(而我不清楚 main thread 會不會加重,開出的其他 thread 不與它平分
但若加重也是這兩個比較的 main thread 都加重)
當然事實上可能不是大家都 free run
寫 multithread 時 free run 會很恐怖吧?我都會加 sleep
而根據 sleep 的時間,如果某 thread sleep 特長,長到奪得控制權的機率並非平均
上述算式就差更多;但基本上我狠狠的加大 working thread 的 sleep 時間並不是問題
: loop.run_in_executor 的本質是 concurrent.futures 底下的 ThreadPoolExecutor
: ,當然你也可以改用 ProcessPoolExecutor ,這跟你原本的 threads 改成 Process
: 一樣,唯一的差別是 Executor 用的是 Worker model。
: 回過頭來說,照你原本 30 threads 的版本,可以先試著用 pypy 跑看看能不能加速
: Python code 執行的速度,減輕 GIL 的影響 (沒錯, pypy 也是有 GIL);另外的選
: 項用 Cython 把 threads 用 release_gil 加速。
: 上面兩個選項都是最小改動,要完全避免 GUI freezing 的話就是把 threads 移到
: 另一個 Process,變成 Thread(GUI) <-> Process (Threads) 的架構,但如果你在
: 各個 threads 之間有 sharing data , IPC 的成本不見得會比較低。
1. 取消 GIL 後,我的程式就要面臨 thread safe 的挑戰;目前都沒安排
GIL 為什麼被採用?當我寫了一陣子 python 後我發覺,它不像 C 是要高效的
它釋放我的腦袋在處理邏輯上,當我緊張的寫一小段程式,要看它會不會當機時
C 的寫法會在 thread 裡撞變數,一定要 critical section
而 python 的寫法不會,可以說 python 的 ATOM 就是很大顆,蠻橫但好用
2. 切成兩個 Process 是我前面也談過的架構, IPC 是一定要的,比如跨網路
由遠端網頁連入近端,那麼近端只要運作所有 working thread,根本不用 GUI
這部份完成了啊,我的 UI 不讓 working thread 直接取值的
UI 只寫入 MySQL, working thread 只從 MySQL 取資料就開始跑
所以我只要運作起 MySQL, 它就替我做完我要的 IPC 了...
開放網路連入 MySQL server,就變遠端控制..
來說說 GIL 我看到的經典差別是什麼
以兩軸馬達控制器來說,C 寫的可以完美的走一條斜線
而 python 寫的,則是會抖動的走一條斜線;要嘛走 x 軸,要嘛走 y 軸
就是不同時走
當然用點技巧解掉就不會了,總是得刻意用最簡單的寫法來強調 GIL 的影響
我這段反白,和你上面那段紅色的,差別是一個用專有名詞,一個用口語描述
如果你說我這樣叫做不懂,那差別在哪裡?
(差別在 google 搜尋方案時,有專有名詞比較精準;當然這是好事)
不過我還要說一個我面臨的問題,不是 GIL 的影響
而是 GUI framework 一般來說,有一個 'render' 的時間
如果我讓程式進入一個自己的 while loop 永遠跑不完
在 working thread 是沒問題的,但在 UI thread 它就是不繪圖
可以說我的繪圖指令,或者擺放各個 UI 控制項的指令
都只像在安排結構,而這個結構必需等 ui thread 有空時才會被 guizero 解析並繪圖
因此繪圖的反應要快,那每個 callback 就要快點執行完且 return 回 main loop
如果執行快但很快又要執行新的 callback, 留給 framework 的 idle time 不夠長
它就是不繪圖;症狀是我可以用 print 或 log 看到,指令都有執行完但它就是不繪
如果在 windows os, 我會下一道 update window
相對其他類似的 framework,我也都在找這道 update window 指令
要嘛沒找到,要嘛我覺得快點執行完返回 main loop 也不錯,我已經習慣了
GIL 不只要搶到,還要 idle 夠久,否則不只是卡,是畫面根本不出來!
: 當然你也可以寫 C 啦,能用的東西多得是,會不會而已。
: BTW , asyncio 真的不好嗎?當然不是。但 Python 的 async 有傳染性,而且受限於
: GIL ,sync -> async 不實際, async -> sync 更是自找麻煩。
: 糯米摻黏米又怎麼會好呢? 要寫 GUI 不如左轉 nodejs 吧
你提 nodejs 就更可以狠狠的罵我了
不會,完全不會 XD
當年寫 C 時,我是計較千分之一秒的人
現在寫 python,為什麼不堅持 C?
因為一開始專案評估時,老闆就對我說:
我們的 sensor, relay, 你一分鐘運算一次就好
我一聽,這根本是殺雞用牛刀,python 慢歸慢,夠用;來學一下 XD
所以比如切兩個 process 可解,我為何要用 Coroutine 解?
因為它有趣,我要求不高,還可以解
事實上現在我還是寫成 sensor/relay 一秒運算一次
因為如果一分鐘運算一次,我在 debug 時也等得累了
http://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/
傳染性是指這件事嗎?
我想這就是大力向我推薦 Coroutine 的朋友該替我想一下的了
當然我知道大家都不是各領域的業務,只是興趣
他喜歡 Coroutine,想讓我試試 Coroutine
那我試了,碰到問題並回報
如果他不幫我解決這問題,那我以後就不會再考慮 Coroutine
難道大家要接受 Coroutine 是個雞肋這想法?
既然說出要安排個好的架構(但好的架構就是排除 Coroutine?)
好啊,那這就是個題目
如果我成功的切成兩大塊,那就不會有很多的混 call
兩大塊間用 async/sync queue 連來連去
就好像程式內的 IPC,這是 async 和 sync 裡的 IPC
那我就真的能用它解決一些問題
而那些問題我未必有描述在文章裡,所以你不知道我有這些要面對
作者: celestialgod (天)   2023-02-08 15:46:00
我不知道有沒有人看得懂 我看不懂
作者: leolarrel (真.粽子無雙)   2023-02-08 16:28:00
我也看不懂...我C寫了25年,看不懂他在說啥.一定是我還不構資深反而zerof 的文我秒懂...反應怎麼那麼激動,我只是說他的內容我看了秒懂.你的我看了三遍還是不懂.所以我說我還不構資深阿
作者: aalexx (aalexx.S)   2023-02-08 17:53:00
framework是一個字
作者: zerof (貓橘毛發呆雕像)   2023-02-08 21:26:00
我的頭好痛 果然回文是浪費時間… 我只想到葵花寶典第一頁你肯定是沒切就開始練multi thread 都沒練好就跑去練 coroutine 我的媽咪啊...GIL 的 paper 也才幾頁 看一下不需要太多時間 用 asyncio (coroutine) 比 threading 更需要理解 time sliding ,更何況 python 的 coroutine model 是 single thread event loop ; 複雜度問題才會很多程式都還是用 multi threads其他我就不說了 background knowledge 不夠 說了也是白搭省點彼此的時間 我當看戲的
作者: leolarrel (真.粽子無雙)   2023-02-09 10:38:00
十年比二十五年資深...... (其他的我沒意見算了我也跟zerof 一樣看戲好了
作者: s860134 (s860134)   2023-02-09 18:18:00
不要浪費時間 半瓶水
作者: zerof (貓橘毛發呆雕像)   2023-02-10 00:20:00
笑死 XD 你先想想 async model 的 coroutine 怎麼接會 syncfunction 的 callback 吧 走火入魔 幫不了你
作者: Yshuan (倚絃)   2023-02-13 11:03:00
一直看到sleep... 就不想認真看了(?
作者: leolarrel (真.粽子無雙)   2023-02-16 10:22:00
喔,寫過windows 3.0的sdk ...
作者: zerof (貓橘毛發呆雕像)   2023-02-16 12:17:00
你從頭到尾都沒有理解問題啊 guizero 用的 callback 不是 async loop, 你要用 async 只能把 event loop 開在另一個 thread 裡面,那麼,async -> sync 要怎麼不 lock event loop呢 ^^直接點說, async 沒有不好,問題在於 Python 的 async model 沒有表面上單純,不然從 3.4 包進 builtin 之後早就變熱門話題了(看看隔壁的 go, js) 你如果連 Python 的 generator, GIL 都不熟,用 async 只是搬石頭砸自己的腳Btw, 年資沒有什麼好說嘴的,知識不是你坐在那邊幾年就會跑進腦袋裡 不如多把關鍵字研究清楚
作者: Sunal (SSSSSSSSSSSSSSSSSSSSSSS)   2023-02-16 18:15:00
通常會用 callback not call back
作者: poototo (poototo)   2023-02-16 21:57:00
GIL會在任意兩行bytecode指令間暫停thread但協程只在await暫停,比thread較多掌控權只是await很頻繁,兩個task仍可能data race

Links booklink

Contact Us: admin [ a t ] ucptt.com