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

看板Python作者 (吹笛牧童)時間1年前 (2023/02/08 14:18), 1年前編輯推噓1(1020)
留言21則, 5人參與, 1年前最新討論串4/4 (看更多)
※ 引述《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 的影響再來比? 有一件事我沒在前面的文章中提及,就是 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, 都用 call back 或 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 解析並繪圖 因此繪圖的反應要快,那每個 call back 就要快點執行完且 return 回 main loop 如果執行快但很快又要執行新的 call back, 留給 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 那我就真的能用它解決一些問題 而那些問題我未必有描述在文章裡,所以你不知道我有這些要面對 -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 1.168.18.180 (臺灣) ※ 文章網址: https://www.ptt.cc/bbs/Python/M.1675837098.A.C3C.html

02/08 15:46, 1年前 , 1F
我不知道有沒有人看得懂 我看不懂
02/08 15:46, 1F
他的文我一開始也沒看懂,後來才發現都我知道的概念 只是我沒用那些字眼 即使都是中文,語境也是要吸收的 ※ 編輯: HuangJC (1.168.18.180 臺灣), 02/08/2023 15:58:34

02/08 16:28, 1年前 , 2F
我也看不懂...
02/08 16:28, 2F

02/08 16:28, 1年前 , 3F
我C寫了25年,看不懂他在說啥.一定是我還不構資深
02/08 16:28, 3F

02/08 16:30, 1年前 , 4F
反而zerof 的文我秒懂...
02/08 16:30, 4F
可是他的文我也覺得講到我本來就懂的部份啊 是他以為我不懂。。 那就真的解掉問題嗎? 舉 chrome 為例子,那又不是沒 GIL 版本的 python 這樣做比較,似是而非吧.. 偷渡了論點你能比較出來嗎? 在做物理實驗時,觀測變數必需獨立 而不是利用改善變數 A,去證實改善變數 B 有效吧? 就好像台灣近年來愈來愈差 於是有人說:二十年前還有聯考,改回聯考台灣就能重返榮耀 是這樣證明的嗎? 怎麼不說時代變了,從前的教育也會不適用 多變數能這樣觀測及證明? 但是普設大學很多人說不好,改回聯考的論點也讓人秒懂 讓你秒懂的東西就是對的?

02/08 17:30, 1年前 , 5F
反應怎麼那麼激動,我只是說他的內容我看了秒懂.你的我
02/08 17:30, 5F

02/08 17:30, 1年前 , 6F
看了三遍還是不懂.所以我說我還不構資深阿
02/08 17:30, 6F
我上次說我 C 的底子十年對吧.. 好吧,我比你資深,沒錯 但我也說了,這時膨風沒有好處 反而只會被電:都這麼資深了這個還不會

02/08 17:53, 1年前 , 7F
framework是一個字
02/08 17:53, 7F
謝謝,因為我英文很爛 XD;等等修

02/08 21:26, 1年前 , 8F
我的頭好痛 果然回文是浪費時間… 我只想到葵花寶典第一頁
02/08 21:26, 8F

02/08 21:26, 1年前 , 9F
你肯定是沒切就開始練
02/08 21:26, 9F

02/08 21:26, 1年前 , 10F
multi thread 都沒練好就跑去練 coroutine 我的媽咪啊...
02/08 21:26, 10F
那你也看看介紹給我的網友怎麼說的 coroutine 的代價比 multi-thread 低... 光論邏輯其實都是把程式切薄片 但實作方法就有差別,也就是這個差別造成它的代價低 那現在你要怎麼給評估建議? '凡是不能寫成純 Coroutine 的,就不建議用 Coroutine'? 倒也不是,前面有板友給我解了,有本事 thread 和 task 混在一起 你也說了,使用 UI framework, 有 callback 是常態 我以為這句的語氣不應是對我,應該是對別人 因為我本來就知道是常態,我一直說我要走通 sync call async 這條路,無法迴避 是別人說我為何把問題搞得這麼複雜 那這時接你這句'使用 UI framework, 有 callback 是常態'不是很能替我說話嗎? 這是我無法避免的挑戰,不是我把問題搞複雜 先不說切成兩個 process 的解法 我自己就有提出,你也提了一次,這沒什麼爭議的 如果你附議'當有 UI 時還寫 coroutine 是自找麻煩' 那這件事就落幕了,當初我說我想自己分配 CPU 時間,網友的建議... 因為他們不知道我有 UI 要寫,所以不能說建議錯 但如果先知道我有 UI,有 sync call back 是否他們根本就不做此建議? 我知道大家只是建議,我自己的工作是自己的責任 只是現在別管怎麼寫了,連'評估'都成為一道題目 所以大家要附議這句嗎:當有 UI framework 時,用 coroutine 是自找麻煩,不值得 (因為我真的可以用兩個 process 寫,而且也鋪陳很久了) 》果然回文是浪費時間 你的態度認為你是對的,是來教訓我而我不受教 所以才會得到浪費時間的結論 那就請你不要回文了,因為如你說的,我自己可以去看那薄薄的文件 我還沒給你錢呢.. 算一下你的時薪,如果要聘你當顧問,我一定出不起 我以為這裡是討論的地方,不是教訓的地方 我之所以不敢亮出自己真實年資,是因為我的實力根本不能彰顯年資 要教訓別人談不上,都是來討論的

02/08 21:35, 1年前 , 11F
GIL 的 paper 也才幾頁 看一下不需要太多時間 用 asyncio (
02/08 21:35, 11F

02/08 21:35, 1年前 , 12F
coroutine) 比 threading 更需要理解 time sliding ,更何
02/08 21:35, 12F

02/08 21:35, 1年前 , 13F
況 python 的 coroutine model 是 single thread event loo
02/08 21:35, 13F

02/08 21:35, 1年前 , 14F
p ; 複雜度問題才會很多程式都還是用 multi threads
02/08 21:35, 14F

02/08 21:36, 1年前 , 15F
其他我就不說了 background knowledge 不夠 說了也是白搭
02/08 21:36, 15F

02/08 21:36, 1年前 , 16F
省點彼此的時間 我當看戲的
02/08 21:36, 16F
我的理解是這樣的 比如一個 thread 說 a = 0102030405060708h (簡單的八個 byte 長度變數) 用硬體觸發 CPU 中斷,則很可能在 a = 01020304h (前四個 byte) 已經存入變數位置時,失去 thread 控制權 (當然這看 compiler 怎麼實作,通常一道機械指令就搬完八個 byte,就不會發生這事 我要討論這個時必需假設實作了一個很爛的 compiler) 但下次 thread 取回控制權時,還得接著存入後面四個 byte,也就是 05060708h 為什麼會知道從這裡繼續存?這就是 context switch 保留了一切的緣故 其中包含了所有的暫存器 就邏輯概念來說,除了 thread 失去控制權幾微秒,其他事一如往常 所以這個 thread 可以無痛的繼續跑下去 既然這麼無痛,那任何時間切都可以,用的是時間中斷 python 的就不是了,比如一次執行一百道 python 指令 所以我說 python 的原子又大又蠻橫,變數 a 的 8個 byte 我是沒在擔心被拆開的 以這樣的程式(虛擬碼)來說 thread1 = while True print('t1 looping') thread2 = while True print('t2 looping') 在 multi-thread 可以正常跑 但在 multk-task,就會出事了 task1 = while True print('t1 looping') task2 = while True print('t2 looping') 因為若 task1 先執行,它就會 hold 住控制權,再也不釋出 至少也得在裡面擺個 asyncio.sleep(0) 那這道指令其實就是切程式薄片的位置 它不是時間引發中斷 》更需要理解 time sliding 它是由工程師自己用 code 決定要在哪裡保留機會切換控制權 我得去協調這些,但如果我有需求,我可以讓切換很少發生;夠用就好 比如我說我們專案是一分鐘執行一次就好 那我放心給每一個 thread 執行兩秒鐘 三十個 thread 都要輪到,輪一次就一分鐘 這也是可以的 不過呢,我的程式不快,相反的是很慢 比如某個動作一做就是這樣 開窗戶 = relay On sleep(七分鐘) relay Off 我要安排的是高達七分鐘的工作 XD 看來看去 Coroutine 很方便,直接塞三行指令完成 如果自己做又不想塞住,那我只好把工作排上時間,丟進 message queue 裡去了 這像你說的要了解更多 time slide? 我所看到的文件是說,要了解 message loop 而且我是從 windows sdk, windows MFC framework 一路學上來的人 接觸了微軟那套 message loop 很久,的確有熟悉的感覺 比如使用者按了鍵盤,就有 onKey event, 滑了滑鼠,有 onMouseMove event 如果我 onKey 處理十秒,那滑鼠也會有被卡住十秒的感覺 onkey 就是得快快處理完 那麼這套 framework 就能讓 user 感覺鍵盤和滑鼠兩件事,像多工一樣 但它們事實上不是 timer 中斷直接觸發 CPU 引起全部暫存器的本文切換 而是一段段完全不會同時執行的 event driven 程式片段 我這段理解,有誤嗎? 》複雜度問題才會很多程式都還是用 multi threads 好啊,不談怎麼寫,只談決策 所以 Coroutine 到底為什麼要發明出來? 1。不好學 2。學了,也不好用 如果你是對的,那你就是在教訓別人,當然你覺得是浪費時間 如果有任何一位大德跳出來說'其實很好用,就算是 UI framework 也很好用' 那這就是討論了,相信對你也就不算浪費時間了 ※ 編輯: HuangJC (123.204.157.162 臺灣), 02/08/2023 23:14:59 ※ 編輯: HuangJC (123.204.157.162 臺灣), 02/08/2023 23:17:29 ※ 編輯: HuangJC (123.204.157.162 臺灣), 02/08/2023 23:22:07

02/09 10:38, 1年前 , 17F
十年比二十五年資深...... (其他的我沒意見
02/09 10:38, 17F

02/09 10:42, 1年前 , 18F
算了我也跟zerof 一樣看戲好了
02/09 10:42, 18F

02/09 18:18, 1年前 , 19F
不要浪費時間 半瓶水
02/09 18:18, 19F

02/10 00:20, 1年前 , 20F
笑死 XD 你先想想 async model 的 coroutine 怎麼接會 sync
02/10 00:20, 20F

02/10 00:20, 1年前 , 21F
function 的 callback 吧 走火入魔 幫不了你
02/10 00:20, 21F
文章代碼(AID): #1Zupwgmy (Python)
文章代碼(AID): #1Zupwgmy (Python)