55、【Ubuntu】【Gitlab】拉出内网 Web 服务:http.server 单/多线程分析(七)

【声明】本博客所有内容均为个人业余时间创作,所述技术案例均来自公开开源项目(如Github,Apache基金会),不涉及任何企业机密或未公开技术,如有侵权请联系删除

背景

上篇 blog
【Ubuntu】【Gitlab】拉出内网 Web 服务:http.server 单/多线程分析(六)

提到了并发和并行是两个概念,其中并发是多个任务交替推进,而并行是多个任务物理上同时进行,并分析了 GIL 锁限制 Python 字节码并行执行的原因,下面继续

Python http.server 单/多线程分析

上篇 blog 分析了 CPython 通过一把全局大锁 GIL,限定了同一时刻只能有一个线程能持有 GIL

OK,那么现在又产生了新的问题,如果给每个对象单独加锁,那么是不是就没问题了?这种方案理论上可以,但

  • 内存开销 :如果每个对象都要带一个 mutex 锁,首先内存开销就会爆炸(Python 对象本身就轻量但不紧凑)
  • 性能原因 :除了内存开销外,加解锁不是免费的,即使是无竞争的 mutex,在现代 CPU 上也有几十纳秒的开销,而 Python 是解释型语言,操作频繁 ,频繁地加解锁也会导致性能严重下降(即使单线程也会如此),保守估计性能要下降 10% ~ 30%
  • 死锁风险 :细粒度锁需要精心设计锁顺序,否则极易发生死锁现象(两个或多个线程互相等待对方释放资源资源锁,结果最后所有涉及的线程都无法继续执行,程序卡死) ,而 Python 的动态特性(比如任意对象可以作为字典 key,任意属性可以被修改),使得静态分析锁依赖几乎不可能,而且异常处理和垃圾回收机制等也会因为锁机制会变得极其复杂,造成死锁风险增加
  • 历史与设计哲学 :Python 诞生于上世纪 90 年代,当时多核 CPU 尚未普及,而第一款消费级双核 CPU 是 2005 年的 Pentium D,此外,CPython 的核心原则之一就是简单,清晰,高效(对单线程而言),因此引入了 GIL,用一把粗粒度的大锁保护整个解释器状态,牺牲多线程并行,换取单线程性能和实现简单性

可以看到,GIL 锁之所以限制 Python 字节码的并行执行,根本原因是为了保护 CPython 内部数据结构的线程安全,尤其是引用计数机制(上篇 blog 介绍的)

另外,上面提到,Python 是解释型语言,操作频繁,这里通过对比 C 语言(编译型语言),再详细展开分析下

首先,在 C 语言中,list.append(x) 这样的高级操作不存在,用户必须自己管理数组,内存和边界等,比如下面有一个循环在频繁写入数组

c 复制代码
for (int i = 0; i < N; i++) {
	arr[i] = i;
}
  • 首先,这里会编译成几条高效的机器指令(比如 MOV,ADD,CMP 等)
  • 另外,这里没有函数调用,没有动态类型检查,也没有内存分配(如果预分配了)
  • 如果用户真的要加锁(比如多线程写共享数组),通常只在临界区外加一次锁,而不是每次赋值都加锁,比如
c 复制代码
enter_critic_area()
for (int i = 0; i < N; i++) {
	arr[i] = i;
}
exit_critic_area()

即使不是循环操作,也要加临界区进行保护

c 复制代码
int g_var = 0; # 全局变量
enter_critic_area()
g_var = 5;
exit_critic_area()

可以看到,C 语言的操作是底层的,更细粒度的,由程序员显式控制的,即便是最简单的赋值操作,也不是线程安全的,用户需要显式处理所有并发问题,没有魔法保护

对于 Python 而言,比如 lst.append(x) 这样的高级操作看似简单,但在 CPython 解释器内部会触发

  • 查找 lst 对象(名字解析)
  • 加载 .append 方法(属性查找)
  • 调用 .append 方法(函数调用开销)
  • 检查参数类型
  • 可能触发内存 realloc 再分配(如果列表满了的话)
  • 更新列表长度
  • 增加新元素的引用计数

等一系列操作,这些操作都发生在这一次高级操作中,而这个操作在字节码层面就是一条 LIST_APPENDCALL_METHOD 指令,可以看到,Python 的操作是高层的,更粗粒度的,隐式包含了大量运行时逻辑的,相当于对 C 语言的多个操作进行了一次封装

最后总结一下

  • 从语义粒度(程序员视角) :对 C 语言而言,arr[i] = i 就是一个简单赋值其背后不涉及对象创建,类型检查,内存管理等高层抽象逻辑 , 而 Python 中的 list.append(x) 则是一个高级操作封装了扩容,引用计数,类型安全,内存分配等复杂逻辑。
  • 从执行粒度(CPU,指令视角) :C 语言的粒度更细 ,一条 C 赋值语句只有几条机器指令movadd 等),每条语句都可以被精确控制 ,而一条 Python 字节码(比如 LIST_APPEND)则可能包含数十,甚至上百条 C 指令,里面有函数调用,条件分支,内存操作等多种内容

所以,Python 的操作在语义上更小,但运行时开销更大 ,也就是上面说的,操作更频繁


OK,本篇先到这里,如有疑问,欢迎评论区留言讨论,祝各位功力大涨,技术更上一层楼!!!更多内容见下篇 blog

相关推荐
Watermelo6175 小时前
如何优雅地导出 VS Code 项目目录结构
前端·javascript·vue.js·vscode·算法·性能优化·node.js
艾小码5 小时前
前端性能加速器:Vue Router懒加载与组件分包的极致优化
前端·javascript·vue.js
Moment5 小时前
使用 Tiptap 编写一个富文本编辑器为什么对很多人来说很难 🤔🤔🤔
前端·javascript·github
抹茶冰淇淋6 小时前
降级系统后,2019年的Mac电脑重获新生
前端
雪碧聊技术7 小时前
前端VUE3项目部署到linux服务器(CentOS 7)
前端·linux部署vue3项目
酒尘&13 小时前
JS数组不止Array!索引集合类全面解析
开发语言·前端·javascript·学习·js
学历真的很重要13 小时前
VsCode+Roo Code+Gemini 2.5 Pro+Gemini Balance AI辅助编程环境搭建(理论上通过多个Api Key负载均衡达到无限免费Gemini 2.5 Pro)
前端·人工智能·vscode·后端·语言模型·负载均衡·ai编程
用户479492835691514 小时前
"讲讲原型链" —— 面试官最爱问的 JavaScript 基础
前端·javascript·面试
用户479492835691514 小时前
2025 年 TC39 都在忙什么?Import Bytes、Iterator Chunking 来了
前端·javascript·面试