前言

其实在写 Python 代码前,我并没有学过怎么写 Python,实习写了大量 Python 代码的时候也没有系统学习过 Python 这门编程语言。但是并不影响我使用他,不是说我在吹嘘自己,而是这门语言上手起来真的很简单。这门语言给我最大的感触就是他简单易上手,但是这个简单往往不是看上去的那样简单。

Python 是一个解释型语言,程序执行时由解释器来解释执行。Python 的类型是动态类型,但是 Python 是强类型语言。

说点题外话,我之前和高中同学探讨过家教的问题。我也思考过自己如果做家教肯定教不了物理(大学物理挂科)化学(高中天天被班主任锤)生物(忘光了),初中 小学数学么勉强教一教。实习的同事说他的女儿报了很多班,又是小提琴班,又是健脑班的。我一想我还是比较擅长写程序的,如果我教小学生写 Python 肯定没问题吧。

教会一个小孩子写 Python,需要多少节课呢?

一开始我觉得一天就行了,考虑到有些人刚开始接触,一个星期总没问题了吧。

什么叫做教会,教会的标准是什么?

我觉得带他们入门就可以了,比如说会写一些简单的爬虫,或者构造一个简单的后台系统。这样就会需要教他们一些网络的基本知识了,然后数据库的 CURD 也得会吧。这样子算下来,一周时间能教会多少,我的心里就有了一丝疑问。

但是我并没有试验过,也不感兴趣。我并不是鼓吹计算机网络、操作系统、数据库等等的知识多么多么重要,本文也不探讨这些问题,不会这些不代表会写代码,会这些也不代表写得来代码。会不会写代码与会不会这些基础知识是没有联系的,会不会写代码和能不能写出高质量代码也是不相关的,会不会基础知识与能不能写出高质量代码也不能扯上关系。你的代码质量高不高也不是你说了算的(2333)

看来教育也是一件令人头疼的事情呢。

扯了半天,我想回到我想总结的问题上。我先简单介绍和整理一下几个相关的概念吧。

进程 Process

进程(Process)在教课书上告诉我,进程是程序的一次执行。一个进程拥有自己的 text region、data region、stack region。在我看来,进程的概念是为了优化 CPU 等计算资源的而产生的。进程切换时,CPU 的寄存器等上下文会被切换,所以就需要进程内部存下这个值。随着硬件的提升,我们需要在软件层面对硬件进行多路复用,这才导致了进程的出现和操作系统的发展。

如果说没有进程这个概念,或者说进程不切换,你的双核电脑就只能边听歌边写代码。而不能在后台再下载个小电影啥的。显然不是很合理。

线程 Thread

假设一个程序的一次运行,即一个进程占用了一个 CPU,完全独占,我需要这个程序去运算 100 次。比如说抓取 100 个网页的信息。网络请求是很慢的,起码没有 CPU 处理这些数据快,比如说你访问网络环境差的地方的网站,比你渲染这个页面慢多了。

比如说你烧一百壶水量不等的开水,你可以按照某种顺序一壶一壶烧,烧开,倒水,换一壶;也可以一次性烧 100 壶水,然后谁开了,就把水倒出来。显然后面一种情况,绝大多数情况下更快一点。

线程(Thread)其实就是对进程的一个更小粒度的划分。

多进程 多线程

多进程、多线程描述的是,同样的一堆实例干同一件事的情况。一台机器跑多个相同脚本是多进程,多台机器跑同一个相同脚本也是多进程。因为往往进程与进程之间不存在耦合关系。假设进程之间需要沟通,共享一些东西。我们推荐使用多线程的方式来进行。共享的资源往往能够被直接访问到,因为他们可以拥有相同的一些上下文。但是一台电脑的资源是有限的,我们一台电脑不可能有无限多的 CPU ,所以我们常常需要电脑与电脑之间通讯才能达成协作,这个时候又需要多进程和进程间通讯来完成了。

选择多进程和多线程是一件按需的事情,往往也没有谁好谁不好的问题。

这之中的关系其实有点像多核 CPU 和 单核 CPU 的类比。多核 CPU 可以访问一块内存,而多个单核 CPU (这里需要看成是不同机器)之间需要通过其他方式(网络请求)来达到相同的目的。

我们可以发现,在硬件条件允许的情况下,根本不用考虑这个问题,但是现实情况就是我们需要通过有限的硬件来优化结果。

GIL

全局解释器锁(英语:Global Interpreter Lock,缩写 GIL),是计算机程序设计语言解释器用于同步线程的一种机制,它使得任何时刻仅有一个线程在执行。即便在多核心处理器上,使用 GIL 的解释器也只允许同一时间执行一个线程。常见的使用 GIL 的解释器有 CPython 与 Ruby MRI。

咋一看 GIL 是会影响性能的。

然而 Python 并不一定有 GIL,GIL 并不是 Python 的特性,但由于 CPython 是大部分环境下默认的 Python 执行环境,所以一般大家的 Python 都会有 GIL。

有关 GIL 的资料有很多(企图 抛砖引玉 偷懒)。

解决方案之一呢,就是使用 multiprocess 替代 thread,但是就会出现之前说到的上下文不同的问题,会导致一些变量无法共享(需要非常痛苦的多线程 code 来解决)。

JIT

JIT 是 Just-in-time compilation 的缩写。通常程序有两种方式执行,静态编译和动态解释。前者的效率往往比后者要高。所以 JIT 技术就大概是将动态解释变成静态编译。

例如如下代码,其中包含了一些计算,优化起来的效果是比较好的。

def add(num_a, num_b):
    return num_a + num_b

但是 JIT 往往在 I/O 密集的代码段中的表现就不那么尽如人意了。

def read_file(file_name):
    with open(file_name) as f:
         for line in f: 
            print(line)

如果你的代码想在不更改任何一行代码的情况下,做一些优化,不妨尝试一下 PyPy。


上山不易,披荆斩棘才能善始;下岭更难,临渊履薄方可令终