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