回复内容:
早期 linux 也是大内核锁,进内核态就整个上锁,用户态可以并行。 Big Kernel Lock 在语义上的好处是不存在死锁,既有的非多线程代码可以利用一点多核的优势。“更好的设计” 并不有趣,只能一点一点地一粒一粒地替换成细力度的锁,这份工作在 linux 社区好像做了很多年。但是 linux 好在并不对 API 做承诺,谁修改了接口,谁就可以自己去修改所有的扩展,不在主线里的扩展不 care,挂就挂,谁让他们不进主线呢。
但Python/Ruby 这种基于C 的三方库文化浓厚的松散社区,接口的改动成本太高了,何况效益并不一定显着,比如:
- 确保细粒度锁之后,原本的单线程脚本的性能没有影响?
- 用三方库死锁了怎么规范?
- 网络 IO 任务可以用协程缓一缓,那我们弄多线程图啥?计算密集谁用 python?
如果是新语言,躲开GIL 则相对容易,限制C扩展的接口只走FFI,不暴露解释器的内部实现细节,锁粒度的粗细就只是实现层面的问题了。但这要求语言的性能足够快,这时又成了一个性能和发布时间的权衡。 GIL算不得低效的设计。其坏处在于锁的粒度太粗。在我看来Lua的设计就很好。
Lua也有类似于GIL的结构,但锁的范围仅限于一个runtime环境。而一个OS进程内可以同时存在多个lua runtime环境。这使得一个lua环境里同一时间只能有一个正在执行的语句。想要OS进程内的并行执行,可以同时开很多个Lua环境,他们之间可以做通信。
所以Lua只锁住一个Lua环境,OS进程里可以有多个Lua环境一起执行。而Python GIL的问题在于他是OS进程里全局的。导致了不能同时执行Python语句。
一个环境里用锁来同步使得虚拟机的设计变的简单和高效。这本身无可厚非。 Java在早期也是如此,但后来Java花了很大的精力在环境里做了更细粒度的锁。使得Java可以在一个虚拟机环境里并行执行多个线程。这使得Java的虚拟机变的复杂了许多,性能也会受到影响,因为代码并不好预测哪些变量和资源需要放到锁保护的区域里,只能全都检查一遍。而将虚拟机环境做一个全局锁就不需要检查每一个变量和资源了。
所以我认为好的设计。就是将GIL锁的范围从OS进程的全局改为虚拟机全局。使得一个OS进程里可以同时存在多个虚拟机。每个虚拟机里因为GIL的存在只能使用一个CPU核心。但多个虚拟机使得整个OS进程可以利用多个CPU核心,同时因为在一个OS进程内,数据交换可以直接用引用传递,不涉及内存拷贝带来的巨大开销。 加细粒度的lock实际上会让CPython跑单线程的程序时变慢,楼上一个答案说的还不如加个大锁就是这意思。 。
大多数人觉得GIL不好,移除了多线程就能跑满多核了,这个思考方式有问题。 。 。移除GIL会显着增加CPython实现复杂度,抛开这个不提,写共享内存多线程程序的难度依然不会下降,你在C里会跌的坑,在这里一个不会少。
所以顺其自然吧,反正CPython水平也就那样,就不是冲着性能去的。 。 Python想占满多核也简单,做科学计算的话,用Theano/NumbaPro/numexpr优化numpy计算或者Cython裸写多线程都行,做高并发服务器的话,multiprocessing直接spawn出子进程来搞啊,而且像pulsar这种框架都帮你做好了 细粒度锁 STM TLS 呀:
- 用细粒度锁来解决 GIL,如今的 mutex 使用 futex 在用户态实现,比原来快很多了。
- 用STM 来消减核心路径细粒度锁的开销
- 用TLS 来规避time.time, random.randint 之类的全局函数访问同一个资源时发生的抢占
---------------
接@fleuria Linux内核用了更细粒度锁的问题,为啥Linux 用更细粒度锁不会影响性能,而Python用细粒度锁在没有竞争时又会导致额外开销呢?
因为内核用细粒度锁是spin_lock,即cmpxchg while,在多核之间的竞争开销几乎为零,而同核之间并不会出现spin_lock的开销,因为单核内开始spin_lock 之前,一般都是把本cpu核心的irq 给disable掉的,见Linux irq_disable() / local_irq_disable() 等函数的实现,在允许irq 前不会发生时钟中断导致任务切换。
而用户态能不能用 spin_lock呢?几乎不能,因为单核之间如果进入了 spin_lock 没有 unlock时被时钟中断给切走了,那么新调度进来的任务将会进入 spin_lock 的 while (cmpxchg(...) == xxx) ; 循环并且必须不停循环直到几十毫秒后一个时钟片结束被切回到第一个任务里 spin_unlock了,第二个任务才能获得锁并结束循环。这就是因为用户态无法把 irq 给 disable 掉,不能禁止时钟中断导致的任务被动切换后会进入上面的 spin_lock 单核内耗状态。
所以用户进程还得使用比内核 spin_lock irq_disable/enable 组合慢很多倍的 mutex。索性 mutex有了用户态实现的 futex ,能在 lock时用 cmpxchg 先检测一下,如果没有竞就不争必进入内核态了,而真正有竞争时才进入内核态,所以 mutex 的占用比原来降低了不少。
所以如今有了 futex后,Python 在用户态用更细粒度锁是有可能的,占用比以前少了几个数量级了。然而即便是 futex 的性能也达不到内核态的 spin_lock irq_disable,任然存在一些微弱的开销,那么在核心路径使用 stm 来解决,将会是一个比较好的搭配。
而过分暴露 C 接口给应用程序确实会带来一些问题,会使得细粒度锁和 STM 的实现变得更为复杂,工作量非常大,这也是 GIL 存在的真正原因。 -------- 2015-02-17 更新 --------
评论区开始变得有意思了。所以做下补充说明。
关于 ref count 和锁。我举的例子不恰当。
为了避免陷入具体语言实现细节。所以就以简单实际模型来做讨论。
结论是:ref count 来实现 gc 的话,不需要 os 提供的 thread lock 就可以。
gc, 重点是发现 g, 然后 c.
增加引用是不会出现 g 的。增加引用后,其 rec count 值必然 >= 2 (创造 sth 时 ref count 为 1).
ref count 变成 0 后,是不会再变成 1 的。要让一个资源被人引用,首先资源必须存在才行。如果编译器实现是没问题的话,只有在语义上那个资源也是不可见时 ref count 才会变成 0。
interlocked operation 是什么呢?其实就是一个执行期间保持 lock 信号的,目地操作数为 mem 的一些非串指令(加 减 位运算 位测试等),保证多处理器访问共享内存时的独占问题。
这个指令就能避免这样的情况: ref count 为 2 时,两个不同处理器都对其 dec 后结果是 1(应该为0) 。
如何实现 GC?
<span class="k">if</span> <span class="nf">lockDecr</span><span class="p">(</span><span class="o">&</span><span class="n">sth</span><span class="p">.</span><span class="n">ref</span><span class="p">)</span> <span class="n">then</span> <span class="n">free</span><span class="p">(</span><span class="n">sth</span><span class="p">);</span> <span class="cm">/* 实际应用的话, free 前需递归地对所有被 sth 引用的其他资源进行「发现,回收」动作。 */</span>
<span class="cm">/* if 发现 sth 为 G then C(sth) */</span>
替代 GIL 的更好设计是 coroutine(协程)。既然同一时间只能有一个 thread 在运行,不如让语言的虚拟机自己操作 coroutine stack。GIL 是个偷懒的做法,用 C runtime stack 来实现语言的 stack,然后用操作系统的内核调度来模拟 coroutine 自己操作 stack 的实现。推荐一篇文章:Working simultaneously vs waiting simultaneously
注意其中说 GIL isn't great 的部分,提到了 Go 和 Erlang 的 context management。
其中还提到了 Coroutines in one page of C,和 Lua coroutine 的实现类似。
像 Ruby 有 continuation,就可以实现 coroutine 了。不过 full-continuation 的效率比 one-shot continuation (coroutine) 低很多。不知道 Ruby 有没有实现后者。
实在没有 coroutine,可以像 Node.js 那样用回调实现(类似 CPS)。不过 Python 没有 closure 就没办法了。
所以说,GIL 低效,但是还不算太糟糕。糟糕的是 Python 既用 GIL 还没有 closure,Ruby 既有 GIL 还有 continuation,这些放在一起就不搭了。 GIL不是设计,只是对早期没考虑到的问题的fix。所以相反只要考虑到多线程就行了(这里指实现层面,比如拿Ruby打比方jruby是可以支持真正多核的,但是Ruby因为标准库和很多c扩展没有考虑多线程的情况所以加上了GIL)。抛开GIL从语言层面上说的话加入类似Erlang的actor,rust的task之类特性都可以很轻松的写出正确的多线程程序 锁是共享的,更小粒度的lock应当比global 好

Linux终端中查看Python版本时遇到权限问题的解决方法当你在Linux终端中尝试查看Python的版本时,输入python...

本文解释了如何使用美丽的汤库来解析html。 它详细介绍了常见方法,例如find(),find_all(),select()和get_text(),以用于数据提取,处理不同的HTML结构和错误以及替代方案(SEL)

Python 对象的序列化和反序列化是任何非平凡程序的关键方面。如果您将某些内容保存到 Python 文件中,如果您读取配置文件,或者如果您响应 HTTP 请求,您都会进行对象序列化和反序列化。 从某种意义上说,序列化和反序列化是世界上最无聊的事情。谁会在乎所有这些格式和协议?您想持久化或流式传输一些 Python 对象,并在以后完整地取回它们。 这是一种在概念层面上看待世界的好方法。但是,在实际层面上,您选择的序列化方案、格式或协议可能会决定程序运行的速度、安全性、维护状态的自由度以及与其他系

本文比较了Tensorflow和Pytorch的深度学习。 它详细介绍了所涉及的步骤:数据准备,模型构建,培训,评估和部署。 框架之间的关键差异,特别是关于计算刻度的

Python的statistics模块提供强大的数据统计分析功能,帮助我们快速理解数据整体特征,例如生物统计学和商业分析等领域。无需逐个查看数据点,只需查看均值或方差等统计量,即可发现原始数据中可能被忽略的趋势和特征,并更轻松、有效地比较大型数据集。 本教程将介绍如何计算平均值和衡量数据集的离散程度。除非另有说明,本模块中的所有函数都支持使用mean()函数计算平均值,而非简单的求和平均。 也可使用浮点数。 import random import statistics from fracti

该教程建立在先前对美丽汤的介绍基础上,重点是简单的树导航之外的DOM操纵。 我们将探索有效的搜索方法和技术,以修改HTML结构。 一种常见的DOM搜索方法是EX

本文讨论了诸如Numpy,Pandas,Matplotlib,Scikit-Learn,Tensorflow,Tensorflow,Django,Blask和请求等流行的Python库,并详细介绍了它们在科学计算,数据分析,可视化,机器学习,网络开发和H中的用途

本文指导Python开发人员构建命令行界面(CLIS)。 它使用Typer,Click和ArgParse等库详细介绍,强调输入/输出处理,并促进用户友好的设计模式,以提高CLI可用性。


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

EditPlus 中文破解版
体积小,语法高亮,不支持代码提示功能

ZendStudio 13.5.1 Mac
功能强大的PHP集成开发环境

VSCode Windows 64位 下载
微软推出的免费、功能强大的一款IDE编辑器

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

Dreamweaver Mac版
视觉化网页开发工具