多线程

Python的多线程运行,不是真正的多线程同时运行,这是因为Python解释器引入的GIL-全局解释器锁带来的制约。Python可以有多线程,但是每个线程运行的时候都需要等待GIL的释放,然后获得GIL后上锁,在该线程运行的时间内,其他线程无法运行,因为GIL被锁住,等该线程释放GIL后,其他线程就可以执行了。所以,不管CPU有几核,在某一个时间只有一个线程在运行,这就是为什么Python的多线程有时候被诟病为假多线程的原因。

但是,Python多线程还是有其用武之地的,就是在I/O密集型的应用里面,I/O的读取速度是个瓶颈,可以一个I/O读取线程,一个其他线程,这样就可以利用等待I/O的时间内,并行的进行其他数据处理。

假如程序是计算密集型而不是I/O密集型,Python多线程在性能上没有本质的提高。

Python多线程

创建线程threading.Thread

使用threading模块,可以通过创建新的threading.Thread并为其分配要执行的函数来启动新的执行线程:

import threading

def Test():
  print "Hello threading!"

my_thread = threading.Thread(target=Test)

target参数引用要运行的函数(或可调用对象)。在Thread对象调用start之前,线程不会开始执行。

开始线程Thread.start()

my_thread.start() # prints 'Hello threading!'

现在my_thread已经运行并终止,start再次调用将产生一个RuntimeError。如果你想将你的线程作为守护进程运行,需要设置daemon=True,或者在调用之前设置my_thread.daemonTrue,这就会使的该线程在后台作为守护进程静默运行。

加入线程Thread.join()

如果你将一个大的任务分成几个小任务并希望同时运行它们,但需要等待所有这些工作完成之后,继续进行下面的任务,那么Thread.join()就是你正在寻找的方法。

例如,假设你要下载网站的多个页面并将其编译为单个页面。你可以这样做:

import requests
from threading import Thread
from queue import Queue

q = Queue(maxsize=20)
def put_page_to_q(page_num):
    q.put(requests.get('http://some-website.com/page_%s.html' % page_num)

def compile(q):
    # magic function that needs all pages before being able to be executed
    if not q.full():
        raise ValueError
    else:
        print("Done compiling!")

threads = []
for page_num in range(20):
     t = Thread(target=requests.get, args=(page_num,))
     t.start()
     threads.append(t)

# Next, join all threads to make sure all threads are done running before
# we continue. join() is a blocking call (unless specified otherwise using 
# the kwarg blocking=False when calling join)
for t in threads:
    t.join()

# Call compile() now, since all threads have completed
compile(q)

创建自定义线程类

使用threading.Thread类我们可以子类化新的自定义Thread类。我们必须覆盖子类中的run方法。

from threading import Thread
import time

class Sleepy(Thread):

    def run(self):
        time.sleep(5)
        print("Hello form Thread")

if __name__ == "__main__":
    t = Sleepy()
    t.start()      # start method automatic call Thread class run method.
    # print 'The main program continues to run in foreground.'
    t.join()
    print("The main program continues to run in the foreground.")

你看到的输出顺序时这样的

Hello form Thread
The main program continues to run in the foreground.

但假如将t.join()注释掉的话,输出顺序就变为了

The main program continues to run in the foreground.
Hello form Thread

这也验证了我们上面结束的join方法时等所有的线程执行结束后,才会继续下面的任务。

线程间通信

假如代码中有多个线程,有一些数据需要在它们间共享,那么我们需要在它们之间进行安全的通信。这需要使用Queuequeue类型。

from queue import Queue
from threading import Thread

# create a data producer 
def producer(output_queue):
    while True:
        data = data_computation()
        
        output_queue.put(data)

# create a consumer
def consumer(input_queue):
    while True:
        # retrieve data (blocking)
        data = input_queue.get()

        # do something with the data

        # indicate data has been consumed
        input_queue.task_done()

使用共享队列创建生产者和消费者线程,

q = Queue()
t1 = Thread(target=consumer, args=(q,))
t2 = Thread(target=producer, args=(q,))
t1.start()
t2.start()