zoukankan      html  css  js  c++  java
  • 【归纳】Python中的协程,进程,线程

    本文主要归纳3个内容:

    1. Python协程
    2. Python多进程
    3. Python多线程

    Python协程

    基本协程机制介绍

    参考资料:

    • 协程,又称微线程,纤程。英文名Coroutine
    • 协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。(不同于函数调用)
    • 协程相对于多线程的优势:
      • 协程的执行效率高于多线程
      • 协程不需要锁机制:因为只有一个线程,所以不存在同时写变量冲突
    • 多进程+协程,既充分利用多核CPU,又充分发挥协程的高效率,可获得极高的性能

    【示例】生产者消费者模型的协程实现:

    def consumer():
        r = ''
        while True:
            n = yield r
            if not n:
                return
            print('[CONSUMER] Consuming %s...' % n)
            r = '200 OK'
    
    def produce(c):
        c.send(None)
        n = 0
        while n < 5:
            n = n + 1
            print('[PRODUCER] Producing %s...' % n)
            r = c.send(n)
            print('[PRODUCER] Consumer return: %s' % r)
        c.close()
    
    c = consumer()
    produce(c)
    

    执行结果:

    [PRODUCER] Producing 1...
    [CONSUMER] Consuming 1...
    [PRODUCER] Consumer return: 200 OK
    [PRODUCER] Producing 2...
    [CONSUMER] Consuming 2...
    [PRODUCER] Consumer return: 200 OK
    [PRODUCER] Producing 3...
    [CONSUMER] Consuming 3...
    [PRODUCER] Consumer return: 200 OK
    [PRODUCER] Producing 4...
    [CONSUMER] Consuming 4...
    [PRODUCER] Consumer return: 200 OK
    [PRODUCER] Producing 5...
    [CONSUMER] Consuming 5...
    [PRODUCER] Consumer return: 200 OK
    

    注意到consumer函数是一个generator,把一个consumer传入produce后:

    • 首先调用c.send(None)启动生成器;

    • 然后,一旦生产了东西,通过c.send(n)切换到consumer执行;

    • consumer通过yield拿到消息,处理,又通过yield把结果传回

    • produce拿到consumer处理的结果,继续生产下一条消息;

    • produce决定不生产了,通过c.close()关闭consumer,整个过程结束。

    整个流程无锁,由一个线程执行,produce和consumer协作完成任务,所以称为“协程”,而非线程的抢占式多任务。

    Python协程机制介绍

    • Python中使用asyncio实现协程:
    import threading
    import asyncio
    
    @asyncio.coroutine
    def hello():
        print('Hello world! (%s)' % threading.currentThread())
        yield from asyncio.sleep(1)
        print('Hello again! (%s)' % threading.currentThread())
    
    loop = asyncio.get_event_loop()
    tasks = [hello(), hello()]
    loop.run_until_complete(asyncio.wait(tasks))
    loop.close()
    

    执行结果:

    Hello world! (<_MainThread(MainThread, started 140735195337472)>)
    Hello world! (<_MainThread(MainThread, started 140735195337472)>)
    (暂停约1秒)
    Hello again! (<_MainThread(MainThread, started 140735195337472)>)
    Hello again! (<_MainThread(MainThread, started 140735195337472)>)
    

    hello()会首先打印出Hello world!,然后,yield from语法可以让我们方便地调用另一个generator。

    由于asyncio.sleep()也是一个coroutine,所以线程不会等待asyncio.sleep(),而是直接中断并执行下一个消息循环。当asyncio.sleep()返回时,线程就可以从yield from拿到返回值(此处是None),然后接着执行下一行语句。

    把asyncio.sleep(1)看成是一个耗时1秒的IO操作,在此期间,主线程并未等待,而是去执行EventLoop中其他可以执行的coroutine了,因此可以实现并发执行。

    为了简化并更好地标识异步IO,从Python 3.5开始引入了新的语法async和await,可以让coroutine的代码更简洁易读。

    请注意,async和await是针对coroutine的新语法,要使用新的语法,只需要做两步简单的替换:

    • 把@asyncio.coroutine替换为async;
    • 把yield from替换为await。

    Python多进程

    参考资料

    from multiprocessing import Process
    import os
    
    # 子进程要执行的代码
    def run_proc(name):
        print 'Run child process %s (%s)...' % (name, os.getpid())
    
    if __name__=='__main__':
        print 'Parent process %s.' % os.getpid()
        p = Process(target=run_proc, args=('test',))
        print 'Process will start.'
        p.start()
        p.join()
        print 'Process end.'
    

    创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process实例,用start()方法启动。
    join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。

    Python进程池

    from multiprocessing import Pool
    import os, time, random
    
    def long_time_task(name):
        print 'Run task %s (%s)...' % (name, os.getpid())
        start = time.time()
        time.sleep(random.random() * 3)
        end = time.time()
        print 'Task %s runs %0.2f seconds.' % (name, (end - start))
    
    if __name__=='__main__':
        print 'Parent process %s.' % os.getpid()
        p = Pool()
        for i in range(5):
            p.apply_async(long_time_task, args=(i,))
        print 'Waiting for all subprocesses done...'
        p.close()
        p.join()
        print 'All subprocesses done.'
    

    对Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),调用close()之后就不能继续添加新的Process了。
    Pool()函数可以接收一个int参数,用于设定进程池的大小(同时工作的进程数量),其默认值为电脑的CPU核数

    进程间通信

    Python的multiprocessing模块包装了底层的机制,提供了Queue、Pipes等多种方式来交换数据

    Python多线程

    参考资料

    Python的标准库提供了两个模块:thread和threading,thread是低级模块,threading是高级模块,对thread进行了封装。绝大多数情况下,我们只需要使用threading这个高级模块。

    启动一个线程就是把一个函数传入并创建Thread实例,然后调用start()开始执行:

    import time, threading
    
    # 新线程执行的代码:
    def loop():
        print 'thread %s is running...' % threading.current_thread().name
        n = 0
        while n < 5:
            n = n + 1
            print 'thread %s >>> %s' % (threading.current_thread().name, n)
            time.sleep(1)
        print 'thread %s ended.' % threading.current_thread().name
    
    print 'thread %s is running...' % threading.current_thread().name
    t = threading.Thread(target=loop)
    t.start()
    t.join()
    print 'thread %s ended.' % threading.current_thread().name  # current_thread()函数返回当前线程的实例
    

    多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,任何一个变量都可以被任何一个线程修改

    线程之间共享数据最大的危险在于多个线程可能同时更改一个变量,进而导致数据混乱。

    解决该问题的方法是创建线程锁, 线程锁在Python中一般通过threading.Lock()来实现:

    balance = 0
    lock = threading.Lock()
    
    def run_thread(n):
        for i in range(100000):
            # 先要获取锁:
            lock.acquire()
            try:
                # 放心地改吧:
                change_it(n)
            finally:
                # 改完了一定要释放锁:
                lock.release()
    

    当多个线程同时执行lock.acquire()时,只有一个线程能成功地获取锁,然后继续执行代码,其他线程就继续等待直到获得锁为止。

    获得锁的线程用完后一定要释放锁,否则那些苦苦等待锁的线程将永远等待下去,成为死线程。所以我们用try...finally来确保锁一定会被释放。

    最后,注意:Python解释器由于设计时有GIL全局锁,导致了多线程无法利用多核。多线程的并发在Python中就是一个美丽的梦。
    不过,也不用过于担心,Python虽然不能利用多线程实现多核任务,但可以通过多进程实现多核任务。多个Python进程有各自独立的GIL锁,互不影响。

  • 相关阅读:
    [原]three.js 地形法向量生成
    C# 创建XML文档
    <转载>在C#中操作XML(基础操作)
    <转载>Visual C#.NetSocket篇
    <转载>批处理重定向中的秘密
    <转载>最基本的Socket编程C#版
    <转载>在.NET中运行外部程序的3种方法
    <转载>修改Win7远程桌面端口
    <转载>Visual C#.NetTCP篇
    <转载>C#中的委托和事件(续)
  • 原文地址:https://www.cnblogs.com/lokvahkoor/p/11001365.html
Copyright © 2011-2022 走看看