多线程

Jinku Hu 2023年1月30日 2018年7月22日
  1. Python 多线程
  2. 线程间通信
多线程

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()
Author: Jinku Hu
Jinku Hu avatar Jinku Hu avatar

Founder of DelftStack.com. Jinku has worked in the robotics and automotive industries for over 8 years. He sharpened his coding skills when he needed to do the automatic testing, data collection from remote servers and report creation from the endurance test. He is from an electrical/electronics engineering background but has expanded his interest to embedded electronics, embedded programming and front-/back-end programming.

LinkedIn