协程与线程、进程的区别
协程(Coroutine):
协程是一种比线程更加轻量级的存在。它依赖于语言的支持(或者库的支持),允许开发者在单个线程中执行多个“任务”。
协程依赖于异步编程模型,能够挂起和恢复执行点,通常用于I/O密集型任务。
协程的调度完全由程序控制,而非操作系统。
线程(Thread):
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
线程共享进程的堆空间,但每个线程有自己的栈空间。
线程的调度由操作系统控制,可以实现并发执行。
进程(Process):
进程是系统进行资源分配和调度的一个独立单位。
每个进程都有自己独立的代码和数据空间(内存),进程间通信(IPC)需要特定的机制。
异步编程(协程)
python异步编程主要是通过协程实现的。
协程既不是线程,也不是进程。它是一种用户态的轻量级线程,由程序控制其执行,而非由操作系统调度。协程拥有自己的寄存器上下文和栈,但它们共享同一进程中的堆空间,这使得协程切换的开销远小于传统的线程切换。
本质:允许程序在等待某些操作完成时能够继续执行,而不是被阻塞等待。适合IO密集型任务,比如网络请求,文件读写等,这些操作大部分时间都在等待外部资源,而CPU的计算能力没有得到充分利用。异步编程的核心原则是最大限度减少阻塞
原理:
Coroutines协程:异步编程依赖于协程。通过async def定义函数。协程允许在等待操作时,暂停执行让出控制权,给Event Loop处理其他任务。
事件循环(Event Loop):事件循环是异步编程的核心,负责管理和调度执行所有的协程。当协程被暂停时,Event Loop找出可以执行的其他协程并继续进行,知道原始协程可以再次进行。
新概念:
async def: 用于定义一个协程函数。
async with用于创建异步上下文管理器,通常用于管理资源的获取和释放。
await: 用于“等待”一个可等待对象(如另一个协程)完成,期间协程会被挂起,控制权返回给事件循环。
事件循环: 控制协程的调度执行。
两个包:
asyncio:用于异步编程。
aiohttp:用于异步HTTP请求。
代码:
import asyncio
import aiohttp
async def download_page(session, url):
async with session.get(url) as response:#使用异步上下文管理器 async with 发起GET请求,并等待响应
return await response.text()#返回响应的文本内容。
async def process_pages(urls):
async with aiohttp.ClientSession() as session:#建一个异步HTTP会话对象 session
# semaphore = asyncio.Semaphore(10) # 限制并发请求数量为10
tasks = [download_page(session, url) for url in urls]
pages = await asyncio.gather(*tasks)#等待所有任务完成,获取所有页面的内容并存储在 pages 中。
for page in pages:
# 处理每个页面的内容
print(len(page))
urls = ["http://example.com", "https://api.github.com", "https://www.python.org"]
asyncio.run(process_pages(urls)) #asyncio.run()用于运行异步函数作为主程序的入口点,启动事件循环并运行异步任务。
多线程
多线程是通过threading模块实现,允许程序同时运行多个线程。多进程特别适合于CPU密集型任务。
本质:多线程的本质是允许程序在单个进程内并行执行多个任务。在多线程环境中,当两个或更多的线程尝试同时修改某个共享数据时,可能会导致数据不一致的问题。因此,确保对共享资源的访问是同步的,是多线程编程的第一原则。
原理:
- GIL(Global Interpreter Lock) :python(CPython)有一个全局解释器锁的机制,确保任何时刻只有一个线程在执行python字节码。这意味着即使在多核处理器上,python多线程也无法真正实现并行。
- 并发而非并行:python多线程更适合执行I/O密集型任务(文件操作,网络请求),而非CPU密集任务(科学计算、图像处理、3D渲染)
新概念:
- Thread类: threading.Thread是代表一个线程的类。
- 锁(Lock)和信号量(Semaphore): 用于线程同步。
- 条件变量(Condition): 用于复杂的线程间同步。
使用锁实现线程同步
import threading
x = 0
lock = threading.Lock()
def increment():
global x
for _ in range(100000):
lock.acquire()
x += 1
lock.release()
# 创建两个线程
t1 = threading.Thread(target=increment)
t2 = threading.Thread(target=increment)
t1.start()
t2.start()
t1.join()
t2.join()
print(x)
import os
import threading
def test(file_path):
print(file_path)
thread_list = []
files_list = os.listdir('xx')
for file_path in files_list: # 12个任务
t = threading.Thread(target=test, args= [file_path])
thread_list.append(t)
for t in thread_list:
# pool_sema.acquire()
t.start() # 调用start()方法,开始执行
for t in thread_list:
t.join() # 子线程调用join()方法,使主线程等待子线程运行完毕之后才退出
线程池管理线程
import concurrent.futures
def square(num):
return num * num
if __name__ == '__main__':
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
# 使用线程池的map方法,同样按照参数顺序返回计算结果
numbers = [1, 2, 3, 4, 5]
results = list(executor.map(square, numbers))
print("Squared numbers (using threads):", results)
多进程
靠multiprocessing模块来实现。多进程允许同时运行多个程序实例,每个实例运行在自己独立的进程中。
本质:多进程的本质是利用计算机多核处理器的能力,通过创建多个独立的进程来实现程序的并行执行。尽管每个进程有自己独立的执行环境,但在需要时它们之间应该能够有效地通信。multiprocessing模块提供了多种通信机制,包括管道和队列。
原理
Python的multiprocessing模块提供了一个与threading模块相似的API,但它使用子进程而非线程。
每个子进程都有自己独立的内存空间和Python解释器实例,这避免了GIL(全局解释器锁)限制。
代码:
生产者消费者模型
from multiprocessing import Process, Queue
def producer(queue):
items = ["item1", "item2", "item3"] # 多个元素
for item in items:
queue.put(item)
def consumer(queue):
while not queue.empty():
item = queue.get()
print(f"Consumed {item}")
if __name__ == "__main__":
queue = Queue()
p1 = Process(target=producer, args=(queue,))
p2 = Process(target=consumer, args=(queue,))
p1.start()
p2.start()
p1.join()
p2.join()
使用multiprocessing.Pool来将大量图片转换为灰度图:
from multiprocessing import Pool
import cv2
import os
def convert_to_grayscale(image_path):
# 读取图片
img = cv2.imread(image_path)
# 转换为灰度图
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 保存灰度图,这里假设你想在同一目录下保存,可以根据需要调整保存路径
gray_img_path = os.path.splitext(image_path)[0] + '_gray' + os.path.splitext(image_path)[1]
cv2.imwrite(gray_img_path, gray_img)
def main(image_paths):
# 创建进程池
with Pool(processes=os.cpu_count()) as pool:
# 使用map同步并行处理所有图片
pool.map(convert_to_grayscale, image_paths)
if __name__ == "__main__":
# 假设有一个包含所有图片路径的列表
image_paths = ["path/to/image1.jpg", "path/to/image2.jpg", "..."]
main(image_paths)