Python之多线程
一、使用threading模块的Thread类
1.1 介绍
这是 Python 中最基本的创建线程的方法。通过定义一个函数,然后将这个函数作为参数传递给Thread类的构造函数来创建线程。每个线程对象代表一个独立的执行线程。
1.2 代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import threading import time def print_numbers(): for i in range ( 1 , 6 ): print (i) time.sleep( 1 ) def print_letters(): for letter in 'abcde' : print (letter) time.sleep( 1 ) t1 = threading.Thread(target = print_numbers) t2 = threading.Thread(target = print_letters) t1.start() t2.start() t1.join() t2.join() |
1.3 优点
简单直观,易于理解和使用。对于初学者来说,这种方式很容易上手。可以方便地控制每个线程的执行,如启动(start)和等待线程结束(join)。简单易用,适合轻量级的线程管理。
1.4 缺点
当需要创建大量线程时,代码可能会变得冗长。由于全局解释器锁(GIL)的存在,在CPU密集型任务中,多线程可能不会带来性能上的提升。
二、继承threading.Thread类
2.1 介绍
通过创建一个新的类,继承自threading.Thread类,然后重写run方法。在run方法中定义线程要执行的任务。这样创建的类的实例就是一个线程对象。
2.2 代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | import threading import time class PrintNumbersThread(threading.Thread): def run( self ): for i in range ( 1 , 6 ): print (i) time.sleep( 1 ) class PrintLettersThread(threading.Thread): def run( self ): for letter in 'abcde' : print (letter) time.sleep( 1 ) t1 = PrintNumbersThread() t2 = PrintLettersThread() t1.start() t2.start() t1.join() t2.join() |
2.3 优点
对于复杂的线程任务,可以将相关的代码封装在一个类中,使代码结构更加清晰。可以方便地在类中定义属性和方法,方便线程之间的交互和数据共享(通过类的属性)。
2.4 缺点
相对于第一种方法,代码量可能会更多,因为需要创建一个新的类。继承关系可能会使代码的逻辑变得稍微复杂一些,特别是对于不熟悉面向对象编程的开发者。
三、使用concurrent.futures模块中的ThreadPoolExecutor
3.1 介绍
这个模块提供了一个高级的接口来管理线程池。ThreadPoolExecutor可以自动管理线程的创建、复用和销毁。可以通过提交任务(函数及其参数)到线程池来并发执行任务。
3.2 代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import concurrent.futures import time def print_numbers(): for i in range ( 1 , 6 ): print (i) time.sleep( 1 ) def print_letters(): for letter in 'abcde' : print (letter) time.sleep( 1 ) with concurrent.futures.ThreadPoolExecutor(max_workers = 2 ) as executor: future1 = executor.submit(print_numbers) future2 = executor.submit(print_letters) futures = [future1, future2] for future in concurrent.futures.as_completed(futures): try : future.result() except Exception as e: print ( "An error occurred:" , e) |
3.3 优点
它可以有效地管理线程资源,避免了频繁地创建和销毁线程的开销,尤其适用于需要执行大量短任务的情况。可以方便地获取任务的执行结果,并且可以处理任务执行过程中的异常。提供了更高级的接口,易于管理多个线程,适用于多种并发任务。
3.4 缺点
对于简单的场景,可能会觉得使用ThreadPoolExecutor有些过于复杂。如果对线程池的大小(max_workers)设置不合理,可能会影响性能。例如设置得过大可能会导致资源竞争,设置得过小可能无法充分利用多核处理器。同样受GIL限制,适合I/O密集型任务。
四、使用multiprocessing模块
4.1 介绍
虽然这个模块主要用于创建多进程,但也可以用来创建多线程。通过Process类创建进程时,可以设置daemon=True,这样进程就会在后台运行,类似于线程。
4.2 代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | from multiprocessing import Process def task(name): print (f "Process {name}: starting" ) for i in range ( 5 ): print (f "Process {name}: {i}" ) print (f "Process {name}: finishing" ) # 创建进程 process1 = Process(target = task, args = ( "A" ,), daemon = True ) process2 = Process(target = task, args = ( "B" ,), daemon = True ) # 启动进程 process1.start() process2.start() # 等待进程完成 process1.join() process2.join() |
4.3 优点
可以绕过GIL,适用于CPU密集型任务。
4.4 缺点
进程间通信比线程间通信更复杂,资源消耗也更大。
五、使用第三方库
例如threadpool、greenlet等,这些库提供了更高级的线程管理功能。
总结
每种方法都有其适用场景。对于I/O密集型任务,threading和concurrent.futures是不错的选择;而对于CPU密集型任务,则可能需要考虑使用multiprocessing或第三方库。选择哪种方法取决于具体的应用需求和性能考量。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现