zoukankan      html  css  js  c++  java
  • 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)])

      

      

        

  • 相关阅读:
    二分+RMQ/双端队列/尺取法 HDOJ 5289 Assignment
    思维题 HDOJ 5288 OO’s Sequence
    树形DP Codeforces Round #135 (Div. 2) D. Choosing Capital for Treeland
    最大流增广路(KM算法) HDOJ 1853 Cyclic Tour
    最大流增广路(KM算法) HDOJ 1533 Going Home
    最大流增广路(KM算法) HDOJ 2255 奔小康赚大钱
    Complete the Word CodeForces
    Gadgets for dollars and pounds CodeForces
    Vasya and Basketball CodeForces
    Carries SCU
  • 原文地址:https://www.cnblogs.com/wangqj1996/p/9636138.html
Copyright © 2011-2022 走看看