python并发相关

一、进程、线程、协程的区别

  1. 进程(Process):
    • 进程是操作系统中独立运行的一个程序。
    • 每个进程有自己独立的内存空间和系统资源。
    • 进程之间通常需要通过进程间通信(IPC)来进行数据交换。
    • 进程的创建和切换开销较大。
  1. 线程(Thread):
    • 线程是操作系统调度的最小执行单位。
    • 所有线程共享同一进程的内存空间和系统资源。
    • 线程之间可以直接访问共享的内存,因此需要注意线程安全问题。
    • 线程的创建和切换开销较小。
  1. 协程(Coroutine):
    • 协程是一种用户级线程,由程序员控制其调度和切换。
    • 协程的执行过程中可以主动让出或恢复执行,以实现非抢占式的协作式多任务。
    • 协程通常在单线程中运行,不需要进行上下文切换,因此开销较小。

综上所述,进程是操作系统层面的独立执行单元,线程是操作系统调度的最小执行单位,而协程是由程序员控制调度的用户级执行单位。协程相较于进程和线程,具有更小的开销,并且能够实现非阻塞的协作式多任务。需要特别注意的是,python中的线程和Java中的线程是不同的。

  • Python中的线程采用的是全局解释器锁(Global Interpreter Lock,简称GIL)模型。这意味着在同一时间只能有一个Python线程执行Python字节码,无法实现真正的并行执行。GIL的存在导致在CPU密集型任务中,Python多线程的效率可能不如预期。
  • Java中的线程没有全局锁的限制,可以实现真正的并行执行。每个Java线程都有自己的栈空间,可以独立运行,并发性能较高。

这也是为什么python的并发操作离不开进程的原因,因为只有多进程才能使用多核资源

二、使用方式

结合项目情况来看,多进程使用方式:

uvicorn.run(app="api:app", host="0.0.0.0", port=9000, workers=5) #启动服务时指定workers数量,其数量就是进程数量

线程使用方式:

pool = ThreadPoolExecutor(max_workers=4, thread_name_prefix='route_thread')

pool.submit(start)

协程使用方式:

#这种方式使用协程,当遇到IO等待时,其不会全局阻塞。但是代码也是同步执行的

async def method1():

        await method2()

async def method2():

        print("method2")

三、实际使用中遇到的问题和解决方案

1、当发现某个方法存在全局阻塞时,率先查找该方法有没有替代的异步方法。如果有就替换成异步方法,这种情况只需要对代码做修改,性能上没有影响。例如:

retriever.vectorstore.aadd_documents(sub_docs)

可以改为

await retriever.vectorstore.aadd_documents(sub_docs)

2、当某个方法不存在异步方法时,可以使用线程池执行耗时操作并等待线程执行结果。例如:

def read_pdf(file_path):

        from langchain_community.document_loaders import PDFMinerPDFasHTMLLoader

        loader = PDFMinerPDFasHTMLLoader(file_path)

        data = loader.load()[0]

其中 data = loader.load()[0] 就是一个耗时操作,并且其代码库中没有提供异步方法。则可以在上层调用时使用线程池:

future = executor.submit(read_pdf, output_pdf_path)

while not future.done():

        # 可以做一些其他的事情

        await asyncio.sleep(1)

        pass

return future.result()

因为这里的代码需要同步获取调用结果。所以在循环中判断没有返回结果时非阻塞休眠。这样就不会造成全局阻塞,不过也有弊端,那就是该接口的流程执行时间变得更长。

3、当循环中存在耗时操作,并且没有异步方式时,可以在循环中加入非阻塞休眠的代码。同样,原流程执行时间也会变得更长:

for page in pdf:

        for i in page.get_images(full=True):

                item = page.get_image_bbox(i)

                img = fitz.Pixmap(pdf, i[0])

                hash_md5 = hashlib.md5()

                hash_md5.update(img.samples)

                md5 = hash_md5.hexdigest()

                name = md5 + ".png" img.save("images/" + name)

                text = f" ![]({url + name}) " page.insert_text((item.tl + item.br) / 2, text, fontsize=1)

                await asyncio.sleep(0.00001)

四、总结

python官方最建议的并发编程方式还是异步的方式。但是,当其受限于第三方库时,还是需要使用线程或进程解决。不过,需要在使用线程时,考虑整个系统的异步通知机制。不然其在等待结果时同样会造成全局阻塞。同样,单进程在遇到CPU瓶颈时也需要使用多进程,进程的无状态也需要被保障。

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值