并行世界的Python

作为一种解释型的语言,Python的速度并不算慢。如果对速度有很高的要求的话,可以选择用更快的语言实现,比如C或C++,然后用Python调用。Python的一种常见应用场景是实现高级的逻辑。Python的解释器就是用C语言写的,即CPython。解释器将Python转换成一种中间语言,叫做Python字节码,类似于汇编语言,但是包含一些更高级的指令。当一个运行一个Python程序的时候,评估循环不断将Python字节码转换成机器码。解释型语言的好处是方便编程和调试,但是程序的运行速度慢。其中的一种解决办法是,用C语言实现一些第三方的库,然后在Python中使用。另一种方法是使用即时编译器来替换Cpython,例如PyPy,PyPy对代码生成和Python的运行速度做了优化。但是在本书中,我们将研究第三种方法。Python提供了很多可以利用并行的模块,在后面的章节中,我们将着重讨论这些并行编程的模块。

接下来,本章将介绍两种基本概念:线程和进程,以及它们在Python中的表现。

进程是应用程序的一个执行实例,比如,在桌面上双击浏览器图标将会运行一个浏览器。线程是一个控制流程,可以在进程内与其他活跃的线程同时执行。“控制流程”指的是顺序执行一些机器指令。进程可以包含多个线程,所以开启一个浏览器,操作系统将创建一个进程,并开始执行这个进程的主线程。每一个线程将独立执行一系列的指令(通常就是一个函数),并且和其他线程并行执行。然而,同一个进程内的线程可以共享一些地址空间和数据结构。线程也被称作“轻量进程”,因为它和进程有许多共同点,比如都是可以和其他控制流程同时运行的控制流程,说它“轻量”是因为实现一个进程比线程要繁重的多。重申一遍,不同于进程,多个线程可以共享很多资源,特别是地址空间和数据结构等。

总结一下:

  • 进程可以包含多个并行运行的线程。
  • 通常,操作系统创建和管理线程比进程更能节省CPU的资源。线程用于一些小任务,进程用于繁重的任务——运行应用程序。
  • 同一个进程下的线程共享地址空间和其他资源,进程之间相互独立。

在深入研究通过线程和进程管理并行Python模块之前,我们先来看一下Python中是如何使用这两者的。

在Python中使用进程

在大多数操作系统中,每个程序在一个进程中运行。通常,我们通过双击一个应用程序的图标来启动程序。在本节中,我们简单地展示如何在Python中开启一个进程。进程有自己的地址空间,数据栈和其他的辅助数据来追踪执行过程;系统会管理所有进程的执行,通过调度程序来分配计算资源等。

1. 准备工作

在第一个程序中,你需要先确保安装了Python。最新的Python可以从 https://www.python.org/ 下载安装。

2. 如何做…

执行第一个示例,我们需要敲入下面两个代码文件:

  • called_Process.py
  • calling_Process.py

你可以使用Python IDE(3.3.0)来编辑下面的文件:

# called_Process.py
print("Hello Python Parallel Cookbook!!")
closeInput = raw_input("Press ENTER to exit")
print"Closing calledProcess"
# calling_Process.py
## The following modules must be imported
import os
import sys
## this is the code to execute
program = "python"
print("Process calling")
arguments = ["called_Process.py"]
## we call the called_Process.py script
os.execvp(program, (program,) + tuple(arguments))
print("Good Bye!!")

运行例子的方法是,用Python IDE打开 calling_Process 程序然后按下F5。在Python shell看到的输出如下:

同时,系统的终端将看到如下输出:

我们有两个进程运行,按下Enter可以关闭系统终端。

3. 如何做…

在前面的例子中, execvp 函数开启了一个新的进程,替换了当前的进程。注意”Good Bye”永远不会打印出来。相反,它会在当前的系统路径中搜索 called_Process ,将第二个参数的内容作为独立的变量传给程序,然后在当前环境上下文中执行。called_Process中的 input() 仅仅用来管理当前系统的闭包。本节展示了基于进程的并行,我们在后面会介绍更多通过进程(multiprocessing模块)管理并行的方法。

开始在Python中使用线程

如前面章节提到的那样,基于线程的并行是编写并行程序的标准方法。然而,Python解释器并不完全是线程安全的。为了支持多线程的Python程序,CPython使用了一个叫做全局解释器锁(Global Interpreter Lock, GIL)的技术。这意味着同一时间只有一个线程可以执行Python代码;执行某一个线程一小段时间之后,Python会自动切换到下一个线程。GIL并没有完全解决线程安全的问题,如果多个线程试图使用共享数据,还是可能导致未确定的行为。

在本节中,我们将展示如何在Python程序中创建一个线程。

1. 如何做…

我们需要 helloPythonWithThreads.py 来执行第一个例子:

# To use threads you need import Thread using the following code:
from threading import Thread
# Also we use the sleep function to make the thread "sleep"
from time import sleep
# To create a thread in Python you'll want to make your class work as a thread.
# For this, you should subclass your class from the Thread class
class CookBook(Thread):
    def __init__(self):
        Thread.__init__(self)
        self.message = "Hello Parallel Python CookBook!!\n"
    # this method prints only the message
    def print_message(self):
        print(self.message)
    # The run method prints ten times the message
    def run(self):
        print("Thread Starting\n")
        x = 0
        while (x < 10):
            self.print_message()
            sleep(2)
            x += 1
        print("Thread Ended\n")
# start the main process
print("Process Started")
# create an instance of the HelloWorld class
hello_Python = CookBook()
# print the message...starting the thread
hello_Python.start()
# end the main process
print("Process Ended")

运行上面的代码,需要用Python IDE打开 helloPythonWithThreads.py 然后按下 F5。在Python shell中你将看到以下输出:

2. 讨论

主程序执行结束的时候,线程依然会每个两秒钟就打印一次信息。此例子证实了线程是在父进程下执行的一个子任务。

需要注意的一点是,永远不要留下任何线程在后台默默运行。否则在大型程序中这将给你带来无限痛苦。