欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 社会 > 第27篇:Python开发进阶:python多线程与多进程编程

第27篇:Python开发进阶:python多线程与多进程编程

2025/2/2 22:21:55 来源:https://blog.csdn.net/martian665/article/details/145062918  浏览:    关键词:第27篇:Python开发进阶:python多线程与多进程编程

第27篇:多线程与多进程编程

目录

  1. 并发编程概述
    • 什么是并发编程
    • 多线程与多进程的区别
  2. 多线程编程
    • 线程的基本概念
    • 创建和管理线程
    • 线程同步与锁
  3. 多进程编程
    • 进程的基本概念
    • 创建和管理进程
    • 进程间通信
  4. 线程与进程的比较
  5. 全局解释器锁(GIL)
    • GIL的影响
    • 绕过GIL的策略
  6. 异步编程简介
    • 异步与并发
    • asyncio模块
  7. 示例代码
  8. 常见问题及解决方法
  9. 总结

并发编程概述

什么是并发编程

并发编程是一种程序设计范式,允许多个任务在同一时间段内交替执行,从而提高程序的执行效率和响应能力。在现代计算机中,并发编程常用于处理多任务、提高资源利用率和优化性能。

多线程与多进程的区别

特性多线程多进程
基本单位线程(Thread)进程(Process)
内存空间共享同一进程的内存空间每个进程拥有独立的内存空间
资源开销较低,线程之间切换开销较小较高,进程之间切换开销较大
数据共享通过共享内存实现,需注意线程同步通过进程间通信(IPC)实现
稳定性一个线程崩溃可能影响整个进程一个进程崩溃不会直接影响其他进程
使用场景IO密集型任务、需要频繁共享数据的场景CPU密集型任务、需要高度隔离的场景

多线程编程

线程的基本概念

线程是程序执行的最小单位,一个进程可以包含多个线程。这些线程共享进程的资源,如内存空间和文件句柄,但每个线程有自己的执行栈和程序计数器。

创建和管理线程

在Python中,可以使用threading模块创建和管理线程。

import threading
import timedef worker(name):print(f"线程 {name} 开始工作。")time.sleep(2)print(f"线程 {name} 完成工作。")# 创建线程
thread1 = threading.Thread(target=worker, args=("A",))
thread2 = threading.Thread(target=worker, args=("B",))# 启动线程
thread1.start()
thread2.start()# 等待线程完成
thread1.join()
thread2.join()print("所有线程已完成。")

输出

线程 A 开始工作。
线程 B 开始工作。
线程 A 完成工作。
线程 B 完成工作。
所有线程已完成。

线程同步与锁

由于多个线程共享同一进程的内存空间,可能会导致数据竞争和不一致的问题。为了解决这些问题,需要使用**锁(Lock)**来同步线程。

import threadingclass Counter:def __init__(self):self.value = 0self.lock = threading.Lock()def increment(self):with self.lock:temp = self.valuetemp += 1self.value = tempdef worker(counter, num_increments):for _ in range(num_increments):counter.increment()counter = Counter()
threads = []
num_threads = 10
num_increments = 1000# 创建并启动线程
for i in range(num_threads):thread = threading.Thread(target=worker, args=(counter, num_increments))threads.append(thread)thread.start()# 等待所有线程完成
for thread in threads:thread.join()print(f"最终计数值:{counter.value}")  # 应输出: 10000

输出

最终计数值:10000

说明:通过使用锁,确保同一时间只有一个线程可以修改value,避免了数据竞争。


多进程编程

进程的基本概念

进程是程序执行的独立单位,拥有自己的内存空间和系统资源。与线程不同,进程之间相互独立,彼此之间的数据不共享。

创建和管理进程

在Python中,可以使用multiprocessing模块创建和管理进程。

import multiprocessing
import timedef worker(name):print(f"进程 {name} 开始工作。")time.sleep(2)print(f"进程 {name} 完成工作。")if __name__ == "__main__":process1 = multiprocessing.Process(target=worker, args=("A",))process2 = multiprocessing.Process(target=worker, args=("B",))process1.start()process2.start()process1.join()process2.join()print("所有进程已完成。")

输出

进程 A 开始工作。
进程 B 开始工作。
进程 A 完成工作。
进程 B 完成工作。
所有进程已完成。

进程间通信

由于进程拥有独立的内存空间,进程间通信(IPC)需要使用特定的机制,如队列(Queue)、管道(Pipe)等。

import multiprocessingdef producer(queue):for i in range(5):item = f"Item {i}"queue.put(item)print(f"生产者生产:{item}")def consumer(queue):while True:item = queue.get()if item is None:breakprint(f"消费者消费:{item}")if __name__ == "__main__":q = multiprocessing.Queue()producer_process = multiprocessing.Process(target=producer, args=(q,))consumer_process = multiprocessing.Process(target=consumer, args=(q,))producer_process.start()consumer_process.start()producer_process.join()q.put(None)  # 发送终止信号给消费者consumer_process.join()print("生产与消费完成。")

输出

生产者生产:Item 0
生产者生产:Item 1
生产者生产:Item 2
生产者生产:Item 3
生产者生产:Item 4
消费者消费:Item 0
消费者消费:Item 1
消费者消费:Item 2
消费者消费:Item 3
消费者消费:Item 4
生产与消费完成。

说明:使用队列实现生产者和消费者之间的通信,通过在队列中放置None作为终止信号,通知消费者结束消费。


线程与进程的比较

特性多线程多进程
创建开销低,启动和销毁速度快高,启动和销毁速度慢
内存使用共享内存,内存利用率高独立内存,不共享,内存利用率低
数据共享直接共享,需同步机制需使用IPC机制
稳定性一个线程崩溃可能影响整个进程一个进程崩溃不会影响其他进程
使用场景IO密集型任务、需要频繁共享数据的场景CPU密集型任务、高度隔离的任务

选择建议

  • 多线程适用于IO密集型任务,如网络请求、文件读写等,因为线程之间的切换开销较小,可以有效利用等待时间。
  • 多进程适用于CPU密集型任务,如大规模数据处理、复杂计算等,因为每个进程可以利用多核CPU,提高并行计算能力。

全局解释器锁(GIL)

GIL的影响

在CPython(Python的标准实现)中,存在全局解释器锁(GIL),它保证在任何时刻只有一个线程可以执行Python字节码。这意味着即使在多核CPU上,多线程程序也无法实现真正的并行计算,限制了多线程在CPU密集型任务中的性能提升。

绕过GIL的策略

为了绕过GIL的限制,可以采用以下策略:

  1. 使用多进程:由于每个进程有自己的解释器和GIL,多进程可以充分利用多核CPU,实现真正的并行计算。
  2. 使用C扩展或Cython:通过编写C扩展或使用Cython,可以在C层面释放GIL,实现并行计算。
  3. 使用multiprocessing模块:在Python中,multiprocessing模块提供了方便的多进程支持,适用于需要绕过GIL的场景。

异步编程简介

异步与并发

异步编程是一种并发编程范式,通过事件循环和非阻塞IO操作,实现高效的任务调度和资源利用。与多线程和多进程不同,异步编程通常在单线程内通过协程(coroutine)实现并发。

asyncio模块

Python的asyncio模块提供了用于编写异步代码的框架,支持协程、任务、事件循环等。

import asyncioasync def greet(name):print(f"Hello, {name}!")await asyncio.sleep(1)print(f"Goodbye, {name}!")async def main():await asyncio.gather(greet("Alice"),greet("Bob"),greet("Charlie"))if __name__ == "__main__":asyncio.run(main())

输出

Hello, Alice!
Hello, Bob!
Hello, Charlie!
Goodbye, Alice!
Goodbye, Bob!
Goodbye, Charlie!

说明:通过asyncio.gather并行执行多个协程,实现高效的异步任务调度。


示例代码

以下示例展示了如何使用多线程和多进程编写一个简单的网页抓取工具。

项目结构

web_crawler/__init__.pycrawler.py
main.py

web_crawler/crawler.py

# web_crawler/crawler.pyimport requests
from threading import Thread
from multiprocessing import Process, Queue
import timeclass ThreadCrawler:def __init__(self, urls):self.urls = urlsdef fetch(self, url):try:response = requests.get(url, timeout=5)print(f"线程抓取 {url} 状态码: {response.status_code}")except requests.RequestException as e:print(f"线程抓取 {url} 失败: {e}")def run(self):threads = []for url in self.urls:thread = Thread(target=self.fetch, args=(url,))threads.append(thread)thread.start()for thread in threads:thread.join()class ProcessCrawler:def __init__(self, urls, queue):self.urls = urlsself.queue = queuedef fetch(self, url):try:response = requests.get(url, timeout=5)result = f"进程抓取 {url} 状态码: {response.status_code}"except requests.RequestException as e:result = f"进程抓取 {url} 失败: {e}"self.queue.put(result)def run(self):processes = []for url in self.urls:process = Process(target=self.fetch, args=(url,))processes.append(process)process.start()for process in processes:process.join()while not self.queue.empty():print(self.queue.get())

main.py

# main.pyfrom web_crawler.crawler import ThreadCrawler, ProcessCrawler
from multiprocessing import Queue
import timedef main():urls = ["https://www.python.org","https://www.github.com","https://www.invalid-url.com",  # 无效URL"https://www.stackoverflow.com","https://www.google.com"]print("使用多线程进行网页抓取:")thread_crawler = ThreadCrawler(urls)start_time = time.time()thread_crawler.run()end_time = time.time()print(f"多线程抓取耗时:{end_time - start_time:.2f} 秒\n")print("使用多进程进行网页抓取:")queue = Queue()process_crawler = ProcessCrawler(urls, queue)start_time = time.time()process_crawler.run()end_time = time.time()print(f"多进程抓取耗时:{end_time - start_time:.2f} 秒")if __name__ == "__main__":main()

运行结果

使用多线程进行网页抓取:
线程抓取 https://www.python.org 状态码: 200
线程抓取 https://www.github.com 状态码: 200
线程抓取 https://www.invalid-url.com 失败: HTTPSConnectionPool(host='www.invalid-url.com', port=443): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x7f8c1c2d2d90>: Failed to establish a new connection: [Errno -2] Name or service not known'))
线程抓取 https://www.stackoverflow.com 状态码: 200
线程抓取 https://www.google.com 状态码: 200
多线程抓取耗时:2.02 秒使用多进程进行网页抓取:
进程抓取 https://www.python.org 状态码: 200
进程抓取 https://www.github.com 状态码: 200
进程抓取 https://www.invalid-url.com 失败: HTTPSConnectionPool(host='www.invalid-url.com', port=443): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x7f8c1c2d2f40>: Failed to establish a new connection: [Errno -2] Name or service not known'))
进程抓取 https://www.stackoverflow.com 状态码: 200
进程抓取 https://www.google.com 状态码: 200
多进程抓取耗时:2.25 秒

说明:通过多线程和多进程分别抓取多个网页,展示了并发编程在IO密集型任务中的应用。注意,无效的URL会触发异常处理机制,确保程序的健壮性。


常见问题及解决方法

问题1:如何在线程中共享数据?

原因:多个线程需要访问和修改共享数据,可能导致数据竞争和不一致。

解决方法

  1. 使用锁(Lock):在访问共享数据时,使用锁来同步线程,确保同一时间只有一个线程可以修改数据。
  2. 使用线程安全的数据结构:如queue.Queue,它内部实现了线程同步机制,适合多线程环境下的数据交换。
import threading
import queuedef producer(q):for i in range(5):q.put(i)print(f"生产者生产:{i}")def consumer(q):while True:item = q.get()if item is None:breakprint(f"消费者消费:{item}")q = queue.Queue()
producer_thread = threading.Thread(target=producer, args=(q,))
consumer_thread = threading.Thread(target=consumer, args=(q,))producer_thread.start()
consumer_thread.start()producer_thread.join()
q.put(None)  # 发送终止信号
consumer_thread.join()

输出

生产者生产:0
生产者生产:1
生产者生产:2
生产者生产:3
生产者生产:4
消费者消费:0
消费者消费:1
消费者消费:2
消费者消费:3
消费者消费:4

问题2:如何避免死锁?

原因:当多个线程或进程相互等待对方释放资源时,会导致程序无法继续执行,即发生死锁。

解决方法

  1. 遵循锁的顺序:确保所有线程以相同的顺序获取锁,避免循环等待。
  2. 使用定时锁:在获取锁时设置超时时间,如果超时则释放已获取的锁,避免长时间等待。
  3. 减少锁的使用范围:尽量缩小锁的粒度,减少锁的持有时间。
import threading
import timelock1 = threading.Lock()
lock2 = threading.Lock()def thread_a():with lock1:print("线程A获取lock1")time.sleep(1)with lock2:print("线程A获取lock2")def thread_b():with lock2:print("线程B获取lock2")time.sleep(1)with lock1:print("线程B获取lock1")t1 = threading.Thread(target=thread_a)
t2 = threading.Thread(target=thread_b)t1.start()
t2.start()t1.join()
t2.join()

解决方法示例:通过确保所有线程以相同的顺序获取锁,避免了线程A先获取lock1再获取lock2,而线程B先获取lock2再获取lock1,从而避免了死锁。

问题3:如何在多进程中共享数据?

原因:每个进程拥有独立的内存空间,直接共享数据需要使用进程间通信(IPC)机制。

解决方法

  1. 使用multiprocessing.Queue:适合生产者-消费者模型。
  2. 使用multiprocessing.Pipe:适合点对点通信。
  3. 使用共享内存(Value、Array):适合共享简单数据类型。
import multiprocessingdef worker(shared_list, lock):with lock:shared_list.append('data')if __name__ == "__main__":manager = multiprocessing.Manager()shared_list = manager.list()lock = multiprocessing.Lock()processes = []for _ in range(5):p = multiprocessing.Process(target=worker, args=(shared_list, lock))processes.append(p)p.start()for p in processes:p.join()print(f"共享列表内容:{list(shared_list)}")  # 输出: ['data', 'data', 'data', 'data', 'data']

问题4:如何在异步编程中处理异常?

原因:在异步任务中,异常可能不会像同步代码那样立即显现,需要特殊处理。

解决方法

  1. 使用try...except在协程内部捕获异常
  2. 在任务中处理异常,并在事件循环中进行日志记录或其他处理
import asyncioasync def faulty_task():try:await asyncio.sleep(1)raise ValueError("这是一个异步异常。")except ValueError as e:print(f"捕获异常:{e}")async def main():await asyncio.gather(faulty_task(),faulty_task())if __name__ == "__main__":asyncio.run(main())

输出

捕获异常:这是一个异步异常。
捕获异常:这是一个异步异常。

总结

在本篇文章中,我们深入探讨了Python中的多线程与多进程编程。通过理解线程和进程的基本概念、学习如何创建和管理线程与进程、掌握线程同步与进程间通信的方法,以及了解全局解释器锁(GIL)的影响,您可以编写更加高效和健壮的并发程序。同时,介绍了异步编程的基本概念和asyncio模块的使用,帮助您在不同的并发模型中做出合理的选择。

学习建议

  1. 多练习并发编程:尝试编写多线程和多进程程序,理解它们在不同场景下的应用和性能差异。
  2. 掌握同步机制:深入学习锁、信号量等同步机制,确保并发程序的正确性和稳定性。
  3. 理解GIL的限制:了解GIL对多线程程序的影响,合理选择并发模型,提升程序性能。
  4. 探索异步编程:学习asyncio模块的高级特性,编写高效的异步任务,提高程序的响应速度。
  5. 阅读并发编程相关书籍和文档:如《Python并发编程实战》,系统性地提升并发编程能力。

如果您有任何问题或需要进一步的帮助,请随时在评论区留言或联系相关技术社区。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com