导读:Python提供了多种机制来支持并发编程,这些机制包括线程、进程、协程以及异步编程模型,这篇文章让我们一一讨论。
目录
线程与进程
基本概念
进程(Process)
- 进程是操作系统分配资源和调度的基本单位。
- 每个进程拥有自己的内存空间、数据栈以及其他用于跟踪执行的辅助数据。
- 进程间通信(IPC)较为复杂,常见的方式包括管道、信号、套接字等。
线程(Thread)
- 线程是进程内的执行单位,一个进程可以有多个线程,它们共享相同的内存空间。
- 线程之间的通信和数据共享更为容易,但也更容易出现数据同步的问题。
- Python中的线程受全局解释器锁(GIL)的影响,这意味着同一时刻只有一个线程可以执行Python字节码。尽管如此,线程仍然适用于I/O密集型任务。
创建一个进程和线程
使用multiprocessing
模块创建进程
python
import multiprocessing
import os
def square_numbers():
for i in range(5):
print(f" {i} 的平方 {i * i}")
print(f"进程 ID: {os.getpid()}")
if __name__ == '__main__':
# 创建进程
process = multiprocessing.Process(target=square_numbers)
# 启动进程
process.start()
# 等待进程完成
process.join()
print("进程结束")
可以看到,整个任务期间,我们一直是使用创建的进程来执行任务。
使用threading
模块创建线程
创建线程的方法类似:
python
import threading
import time
def print_numbers():
for i in range(1, 6):
time.sleep(1)
print(f"======》 {i}")
# 创建线程
thread = threading.Thread(target=print_numbers)
# 启动线程
thread.start()
# 等待线程完成
thread.join()
print("=======结束")
至此为止,我们已经可以创建单独的进程和线程,并使用它来执行一些我们封装好的函数。但目前看来使用线程或者进程好像没啥用是吧,别急,你现在可以把他当做一个知识点,一个没什么用但是你掌握了的知识点,后面会有大用,在继续之前 我们来补充一点注意事项:
- 线程共享内存空间,而进程拥有独立的内存。这意味着线程之间的通信更简单,但在多线程访问共享资源时需要特别注意同步问题;
- 由于GIL的存在,Python的线程并不能实现真正的并行,但它们适用于I/O密集型任务;
- 一个线程崩溃可能会影响整个进程的稳定性。进程间相互独立,一个进程的崩溃不会直接影响其他进程
协程与异步编程
基本概念
协程
- 协程是一种可以暂停和恢复执行的函数,是异步编程的核心。
- 在Python中,协程是通过
async def
关键字定义的。 - 使用
await
关键字暂停协程的执行,直到等待的操作完成。
异步编程
- 异步编程是一种编程范式,用于非阻塞性操作,特别是I/O操作。
- 它允许程序在等待某些操作完成时继续执行其他代码。
- Python的
asyncio
库是实现异步编程的关键。
实现异步操作
话不多说,上代码:
使用asyncio
进行异步
python
import asyncio
async def count():
print("=====1======")
await asyncio.sleep(1)
print("======2=======")
async def main():
await asyncio.gather(count(), count(), count())
asyncio.run(main())
输出结果:
分析:使用
asyncio
库来运行三个异步协程。在打印完第一次的结果后,睡了一秒,但因为同时有三个协程在工作,所以输出了三个,一秒后,又是三个2打印了出来。
异步I/O操作
异步爬虫必备:aiohttp
python
import aiohttp
import asyncio
async def fetch(session, url):
async with session.get(url) as response:
return await response.text()
async def main():
async with aiohttp.ClientSession() as session:
html = await fetch(session, 'http://www.baidu.com')
print(html)
asyncio.run(main())
咳咳,爬虫虽好,也不要给对方太大压力哦
好了 分析一下它的优势:
非阻塞性,异步编程允许程序在等待操作完成时继续执行,提高了程序的响应性和效率。
资源利用,对于I/O密集型任务,异步编程可以帮助更好地利用系统资源,避免因等待I/O操作而闲置。
简化复杂性,虽然异步编程在一开始可能看起来复杂,但它可以帮助简化某些类型的并发代码。
注意
- 异步编程需要一种不同的思维方式,理解
async
和await
关键字是关键。 - 并不是所有的库都支持异步操作。在选择库时,需要注意是否存在异步支持。
- 错误处理在异步编程中同样重要,需要考虑如何处理异步任务中的异常。
并发难题
数据同步问题
当多个线程或进程尝试同时访问和修改共享数据时,可能会导致数据不一致和不可预测。
解决策略
- 使用互斥锁(Mutex)或其他同步机制(如信号量、临界区)来确保在任一时刻只有一个线程可以访问共享资源。
- 在Python中,可以使用
threading.Lock
来实现线程安全的操作。
demo
python
import threading
class Counter:
def __init__(self):
self.value = 0
self.lock = threading.Lock()
def increment(self):
with self.lock:
self.value += 1
死锁
- 死锁是指两个或多个线程相互等待对方释放资源,从而导致它们都停止执行的情况。
- 死锁通常发生在多个线程需要相同的锁,但以不同的顺序获取它们时。
解决策略
- 避免多个锁的不必要使用。
- 保证所有线程以相同的顺序获取锁。
- 使用超时机制强制释放锁,或检测并打破死锁。
活锁和资源饥饿
- 活锁是指线程虽然没有被阻塞(仍在运行),但无法继续执行有用的工作。
- 资源饥饿发生在某些线程无法获取必要的资源,而其他线程则持续运行。
解决策略
- 重新设计算法和工作流,以确保所有线程都有机会执行。
- 实现合理的资源分配策略和优先级制度。
上下文切换的开销
- 在并发环境中,操作系统经常需要在不同的线程或进程之间进行上下文切换。
- 这些切换可能导致显著的性能开销。
解决策略
- 优化线程数量,避免创建过多的线程。
- 使用线程池来管理线程,减少创建和销毁线程的次数。
实际案例
所有案例不提供业务逻辑部分,只是讨论并发操作。
网络服务器
案例描述
- 网络服务器常常需要同时处理成千上万的客户端请求。
- 使用多线程或异步编程可以有效地提高服务器的响应能力和吞吐量。
代码示例
python
import asyncio
from aiohttp import web
async def handle(request):
return web.Response(text="Hello, world")
app = web.Application()
app.router.add_get('/', handle)
web.run_app(app)
数据处理
案例描述
- 在数据分析和数据科学中,经常需要处理大量数据集。
- 并发编程(特别是多进程处理)可以在这些任务中大幅提高处理速度。
代码示例
python
from multiprocessing import Pool
def process_data(data):
# 数据处理逻辑
return result
if __name__ == '__main__':
pool = Pool(processes=4)
data_to_process = [data1, data2, data3, data4]
results = pool.map(process_data, data_to_process)
Web爬虫
案例描述
- Web爬虫需要从多个网页上快速有效地收集数据。
- 使用多线程或异步编程可以同时发送多个网络请求,显著提高数据收集的效率。
代码示例
(感谢 baidu 提供的友情支持 -狗头) 一般情况不建议大家高并发爬取一个网站,会给对方网站造成很大压力,不要让网站成为网战。
python
import asyncio
import aiohttp
async def fetch_page(url, session):
async with session.get(url) as response:
return await response.text()
async def main(urls):
async with aiohttp.ClientSession() as session:
tasks = [fetch_page(url, session) for url in urls]
return await asyncio.gather(*tasks)
urls = ["http://www.baidu.com/a", "http://www.baidu.com/b", "http://www.baidu.com/c"]
pages = asyncio.run(main(urls))
觉得不错 点个赞吧~