• python 多进程、多线程、协程


    1、python的多线程

      多线程就是在同一时刻执行多个不同的程序,然而python中的多线程并不能真正的实现并行,这是由于cpython解释器中的GIL(全局解释器锁)捣的鬼,这把锁保证了同一时刻只有一个线程被执行。

      多线程的特点:

        线程比进程更轻量级,创建一个线程要比创建一个进程快10-100倍。

        线程共享全局变量。

        由于GIL的原因,当一个线程遇到IO操作时,会切换到另一个线程,所以线程适合IO密集型操作。

        在多核cpu系统中,最大限度的利用多核,可以开启多个线程,开销比进程小的多,但是这并不适合python。

      多线程互斥锁:

        因为线程共享全局变量,所以需要互斥锁去限制线程对全局变量的更改。

        假设,当一个线程在执行到获取全局变量的时候,这个后GIL切换到另一个线程执行,这个时候新的线程为全局变量+1后切换回之前的线程,之前的线程中的全局变量还是+1前的值,所以需要互斥锁。

      为什么有了GIL锁还要互斥锁呢?

        GIL锁只是控制同一时刻下只有一个线程被执行,这并不能控制同一时刻只有一个线程去获取并更改全局变量,所以需要使用互斥锁。

      多线程的实现:

    # 导入threading模块
    import threading
    # 定义全局变量
    i=0
    # 定义互斥锁
    mutex = threading.Lock()
    def a():
        # 申明全局变量i
        global i
        for j in range(2000000):
            # 获取互斥锁
            mutex.acquire()
            i+=1
            # 释放互斥锁
            mutex.release()
    
    def b():
        global i
        for j in range(2000000):
            mutex.acquire()
            i+=1
            mutex.release()
    # 创建线程
    t1 = threading.Thread(target=a)
    t2 = threading.Thread(target=b)
    # 开启线程
    t1.start()
    t2.start()
    # 等待所有线程结束
    t1.join()
    t2.join()
    print(i)

    2、python中的多进程

      python的多线程不能利用多核的优势,如果想要充分的利用多核cpu的资源,python中大部分情况需要使用多进程。

      python多进程的特点:

        进程间不共享全局变量,进程修改的数据仅限于该进程内。

        进程创建和销毁的开销比较大。

        相对于线程,进程更适合与计算密集型操作。

        能充分利用多核的优势。  

      进程间通信:

        既然进程间中不公共享全局变量,那么多进程间怎么进行通信呢?可以使用multiprocessing中的Queue模块,当然也可以使用socket、管道、共享内存等方式。

      多进程的实现:

    # 导入multiprocessin模块
    import multiprocessing
    # 创建队列
    queue = multiprocessing.Queue()
    # 定义全局变量
    a = 0
    # 定义函数
    def work1(num):
        # 获取队列中的数据,如果没有数据,将堵塞
        a = queue.get()
        # 将队列中的数据+2000000次num
        for i in range(2000000):
            a+=num
        # 将数据存放在队列中
        queue.put(a)
        # 打印最终结果
        print("work1",a)
    # 定义函数
    def work2():
        # 申明全局变量a
        global a
        # 将a+2000000次1
        for i in range(2000000):
            a+=1
        # 打印最总结果
        print("work2",a)
    # 将a存放在队列中
    queue.put(a)
    # 创建进程
    p1 = multiprocessing.Process(target=work1, args=(2,))
    p2 = multiprocessing.Process(target=work2)
    # 启动进程
    p1.start()
    p2.start()
    # 等待进程结束
    p1.join()
    p2.join()
    # 获取队列中的数据
    a = queue.get()
    # 打印a
    print(a)

      进程池的实现

      进程池能减少重复创建和销毁进程的开销问题

    # 导入需要的模块
    import multiprocessing
    import time
    import random
    # 定义函数
    def work(num):
        print("num=",num)
        time.sleep(random.randint(0,2))
    # 创建进程池,设置进程的数量
    pool = multiprocessing.Pool(3)
    for i in range(10):
        # 开启进程
        pool.apply_async(work, args=(i,))
    # 设置等待时间,等待所有进程结束
    time.sleep(20)

    3、python中的协程

      在linux中线程就是轻量级的进程,而我们通常也把协程称为轻量级的线程。

      对比进程和协程:

        进程是内核调度,而协程是在用户态调度,所以说进程的上下文在内核态保存恢复,而协程是在用户态保存恢复的,所以协程的开销比进程低。

        进程会被抢占,而协程不会,也就是说协程如果不主动让出cpu,那么其他的协程就没有执行的机会。

        进程所需要的内存比协程大得多

      对比线程和协程:

        线程的上下文切换成本相对于协程来说比较高。

        线程的切换由操作系统来控制,而协程的切换由我们自己控制。

      yield实现协程:

    # 定义两个函数
    def work1():
        while True:
            print("work1")
            # 当程序运行到yield就会暂停,等待下次的next调用,然后继续执行
            yield
    def work2():
        while True:
            print("work2")
            yield
    
    w1 = work1()
    w2 = work2()
    while True:
        # 使用next函数启动
        next(w1)
        next(w2)

       greenlet实现协程:

        greenlet安装:   

    sudo pip3 install greenlet

        code:

    # 导入greenlet模块
    from greenlet import greenlet
    def work1():
        for i in range(10):
            print("work1")
            # 打印过后跳转至协程g2继续执行
            g2.switch()
    
    def work2():
        for i in range(10):
            print("work2")
            # 打印后跳转至协程g1继续执行
            g1.switch()
    
    # 创建协程g1
    g1 = greenlet(work1)
    # 创建协程g2
    g2 = greenlet(work2)
    # 跳转至协程g1
    g1.switch()

      gevent实现协程:

        gevent是基于greenlet的并发网络库,每当有一个协程堵塞的时,程序将自动调度。

        monkey-patching:

          一般称为猴子补丁,这个补丁能直接修改标准库里面大部分的阻塞式系统调用。但是如果在复杂的生产环境中使用了这些标准库,可能就会因为打了补丁而出现奇怪的问题。

        gevent安装:

    sudo pip3 install gevent

        code:

    # 导入所需要的模块
    import gevent
    import time
    from gevent import monkey
    # 猴子补丁,monkey.patch_all()方法将所有的标准库都替换掉
    # 使用猴子补丁褒贬不一,但是官网上还是建议使用patch_all(),而且在程序的第一行就执行
    monkey.patch_all()
    def f(n):
        for i in range(n):
            print(i)
            # 设置延时
            time.sleep(0.5)
            # 如果没有导入monkey模块的话,需要使用gevent.sleep()
            # gevent.sleep(0.5)
    
    # ----------------写法一--------------------
    # 创建greenlet协程对象
    # g1 = gevent.spawn(f,5)
    # g2 = gevent.spawn(f,5)
    # g3 = gevent.spawn(f,5)
    # 等待所有greenlet携程结束后退出
    # g1.join()
    # g2.join()
    # g3.join()
    
    # ----------------写法二--------------------
    gevent.joinall([gevent.spawn(f,5), gevent.spawn(f,5), gevent.spawn(f,5)])

      

      

        

  • 相关阅读:
    wireshark筛选器汇总
    .net中的"异步"-手把手带你体验
    Javascript手记-垃圾收集
    Sqlserver作业-手把手带你体验
    oracle11g重置system密码,外二
    return Acad::ErrorStatus::eOk引发error C2220: warning treated as error
    RegOpenKeyEx和RegSetValueEx返回ERROR_SUCCESS,但注册表未发生变化。
    windows7 阻止copyfile到windows目录的解决办法
    如何让AutoCAD自动加载Arx,比如ArxDbg.arx
    入口点函数的19种消息,AcRxArxApp只处理16种。
  • 原文地址:https://www.cnblogs.com/wangqj1996/p/9636138.html
Copyright © 2020-2023  润新知