python异步编程、多线程、多进程原理

协程与线程、进程的区别

协程(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)

在这里插入图片描述

  • 21
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值