Python多线程编程指南:掌握线程基础与threading模块的使用方法

[id_4406[id_1480[id_1262558437]9195]34]

Python多线程编程指南:掌握线程基础与threading模块的使用方法

[id_1847450207]

使用 threading 模块时,能够通过创建一个新的 threading.Thread 实例,然后为其分配一个函数,以此来启动一个新的执行线程。

 import threading
 
 def foo():
     print("Hello threading!")
 
创建了一个线程对象 my_thread,该线程的目标函数是 foo

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

启动线程

 my_thread.start() # 打印 'Hello threading!'

现在,my_thread 已经开始运行并且已经终止了。如果再次调用 start 这个方法,就会引发一个 RuntimeError 异常。如果你希望让线程以守护线程的方式运行,那么在调用 start() 这个方法之前,可以通过传递 daemon=True 这个参数,或者将 my_thread 的 daemon 属性设置为 True,这样就能使线程在后台默默地运行。

线程同步(Joining a Thread)

在某些情形下,你把一个大任务拆分成诸多小任务,并且期望这些小任务能够并发运行,不过需要等到所有小任务都完成之后再接着进行,在这种时候可以运用 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):
   # 需要所有页面才能执行的神奇函数
   if not q.full():
     raise ValueError
   else:
     print("Done compiling!")
 
 threads = []
 for page_num in range(20):
t 是一个线程,其目标是执行 requests.get 函数,传入的参数是 page_num
   t.start()
   threads.append(t)
 
 接下来,使用 join() 这个方法,以确保所有的线程都运行完成之后,再继续进行后续的操作。
 join()是一个阻塞调用。除非在调用 join 时使用 blocking=False 参数,否则它是阻塞的。
 for t in threads:
   t.join()
 
 # 现在所有线程已完成,调用 compile()
 compile(q)

关于 join() 的工作原理,可以参考这里。

创建自定义线程类

我们可以使用 threading.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 方法会去调用 Thread 类的 run 方法。” 可以改写为:“start 方法会调用 Thread 类的 run 方法。” 或者:“start 方法会把调用 Thread 类的 run 方法作为自身的行为。” 又或者:“start 方法会引发对 Thread 类的 run 方法的调用。”
   print 会输出“主程序在前台持续运行。”
   t.join()
   print()

2: 线程间通信

你的代码中有多个线程,你需要在它们之间安全地通信。

可以使用 queue 模块中的 Queue。

 from queue import Queue
 from threading import Thread
 import logging
 import time
 
 # 配置日志记录
设置日志的基本配置,将日志级别设置为 INFO,设置日志的格式为%(asctime)s ,这是一个时间相关的部分;%(levelname)s ,代表着日志的级别;%(message)s ,则是具体的消息内容。)
 
 # 创建数据生产者
 def producer(output_queue, exit_event):
     while not exit_event.is_set():
         data = data_computation()
         output_queue.put(data)
         logging.info(f"Produced data: {data}")
         time.sleep(1)  # 模拟数据生成的延迟
 
 # 创建消费者
 def consumer(input_queue, exit_event):
     while not exit_event.is_set():
         try:
             # 阻塞地获取数据,超时时间为1秒
data 是从输入队列中获取的,获取的超时时间为1)
             # 对数据进行处理
             logging.info(f"Consumed data: {data}")
             # 表示数据已被处理
             input_queue.task_done()
         except Queue.Empty:
             logging.info(没有数据在队列中,正在等待更多数据。)
 
 # 模拟数据生成函数
 def data_computation():
     return "data"
 
 # 创建生产者和消费者线程,并共享一个队列
 q = Queue()
创建了一个线程事件对象 exit_event 。
 
 # 创建消费者线程
一个名为 consumer_thread 的线程,其目标是执行 consumer 函数,参数为 q 和 exit_event,并且该线程是守护线程。True)
 # 创建生产者线程
生产者线程是由一个目标为 producer 的线程创建的,并且传入了参数 (q, exit_event) 。
 
 consumer_thread.start()
 producer_thread.start()
 
 # 主程序运行一段时间后退出
 time.sleep(10)
 exit_event.set()  # 设置退出标志
 
 # 等待生产者线程退出
 producer_thread.join()
 # 等待队列中的所有任务完成
 q.join()
 # 消费者线程作为守护线程,会自动退出

Section 3: 创建线程池

使用 threading 和 queue:

 from socket import socket, AF_INET, SOCK_STREAM
 from threading import Thread
 from queue import Queue
 
 def echo_server(addr, nworkers):
   print('Echo server running at', addr)
   # 启动客户端工作线程
   q = Queue()
   for n in range(nworkers):
t 是一个线程,其目标是执行 echo_client 函数,参数为 (q,) 。
     t.daemon = True
     t.start()
   
   # 运行服务器
创建一个套接字,使用的地址族是 AF_INET,套接字类型是 SOCK_STREAM,将其赋值给 sock 变量。
   sock.bind(addr)
   sock.listen(5)
   while True:
sock 接受连接,得到 client_sock 和 client_addr
将(client_sock, client_addr)放入 q 中。
 
 echo_server(('',15000), 128)

使用

一个用于并行执行任务的线程池执行器,它可以同时处理多个任务,通过提交任务到线程池中,线程池会自动分配线程来执行这些任务,从而提高程序的执行效率。

 from socket import AF_INET, SOCK_STREAM, socket
 from concurrent.futures import ThreadPoolExecutor
 
 def echo_server(addr):
   print('Echo server running at', addr)
   pool = ThreadPoolExecutor(128)
   sock = socket(AF_INET, SOCK_STREAM)
   sock.bind(addr)
   sock.listen(5)
   while True:
     client_sock, client_addr = sock.accept()
pool 把 echo_client、client_sock 和 client_addr 作为参数进行提交
 
 echo_server(('',15000))

《Python Cookbook》(第 3 版)的作者是 David Beazley 和 Brian K. Jones(O'Reilly)。版权归 David Beazley 和 Brian Jones 所有,其 ISBN 为 978-1-449-34037-7。

4: 多线程的高级用法

本节将包含一些使用多线程实现的高级示例。

高级打印机(日志记录器)

一个线程会打印接收到的所有内容,并且会依据终端宽度来修改输出。很有趣的是,一旦终端宽度出现变化,那些已经被打印出来的输出也会随之被修改。

 import threading
 import Queue
 import time
 import sys
 import subprocess
 from获取终端大小的 backports.shutil 相关操作import get_terminal_size
 
 printq = Queue.Queue()
 interrupt = False
 lines = []
 
 def main():
ptt 是一个线程,其目标是 printer 。# 打开打印机
   ptt.daemon = True
   ptt.start()
 
   # 一些要打印的示例内容
   for i in xrange(1,100):
     printq.put(' '.join([str(x) for x in range(1,i)])) # 向打印机发送内容的实际方式
     time.sleep(.5)
 
 def split_line(line, cols):
   if len(line) > cols:
     new_line = ''
     ww = line.split()
     i = 0
     while len(new_line) <= (cols - len(ww[i]) - 1):
       new_line += ww[i] + ' '
       i += 1
       print len(new_line)
     if new_line == '':
       return (line, '')
     return (new_line, ' '.join(ww[i:]))
   else:
     return (line, '')
 
 def printer():
   
   while True:
获取终端的尺寸,将其分别赋值给 cols 和 rows 。# 获取终端尺寸
     msg = '#' + '-' * (cols - 2) + '#\n' # 创建
     try:
使用 printq 的 get_nowait 方法获取到的内容转换为字符串后赋值给 new_line 。
       if new_line != '!@#EXIT#@!': # 优雅关闭打印机线程的好方法
         lines.append(new_line)
         printq.task_done()
       else:
         printq.task_done()
         sys.exit()
     except Queue.Empty:
       pass
 
     # 构建要显示的新消息,并拆分过长的行
     for line in lines:
       res = line # 下面是为了拆分行,如果行长于 cols
       
       # 拆分行
       while len(res) !=0:
2. 分割后的结果赋值给 res 和另一个变量(这里未明确给出另一个变量的名称)。
         msg += '\n' + toprint
 
     # 清空终端并打印新输出
     subprocess.check_call('clear') # 保持终端干净
     sys.stdout.write(msg)
     sys.stdout.flush()
     time.sleep(.5)

5: 使用 while 循环的可停止线程

 import threading
 import time
 
 class StoppableThread(threading.Thread):
   有一个带有 stop() 方法的线程类。这个线程自身需要时不时地去检查 stopped() 这个条件。
   
   def __init__(self):
调用父类 StoppableThread 的构造函数,并将 self 作为参数传递进去。
self 拥有一个名为 _stop_event 的对象,这个对象是 threading 模块中的 Event 类的实例。
 
   def stop(self):
     self._stop_event.set()
 
   def join(self, *args, **kwargs):
     self.stop()
调用父类 StoppableThread 的 join 方法,并传入参数 *args 和 **kwargs 。
 
   def run():
     while not self._stop_event.is_set():
       print("Still running!")
       time.sleep(2)
     print("stopped!")

基于[这个问题](

你可能想看:
« 上一篇
下一篇 »

相关推荐

python爬虫教程PDF,Python爬虫书PDF

2026年09月02日

31阅读

外国教程python,python编程教学视频

2026年09月02日

25阅读

python简明教程沈洁元,python详细基础教程

2026年09月02日

21阅读

python库包教程,python 包

2026年09月02日

26阅读

python教程大全集,python教程600集

2026年09月02日

26阅读

python入门教程完整版,python零基础入门全套教程视频

2026年09月02日

22阅读