zoukankan      html  css  js  c++  java
  • Python多进程、多线程和协程简介

    进程和线程

    进程是一个执行中的程序。每个进程都拥有自己的地址空间、内存、数据栈以及其他用于跟踪执行的辅助数据。在单核CPU系统中的多进程,内存中可以有许多程序,但在给定一个时刻只有一个程序在运行;就是说,可能这一秒在运行进程A,下一秒在运行进程B,虽然两者都在内存中,都没有真正同时运行。

    线程从属于进程,是程序的实际执行者。一个进程至少包含一个主线程,也可以有更多的子线程。Python可以运行多线程,但和单核CPU多进程一样,在给定时刻只有一个线程会执行。

    Python 提供了多个模块来支持多线程编程,包括thread、threading 和Queue 模块等。程序是可以使用thread 和threading 模块来创建与管理线程;推荐用threading模块,它更先进,有更好的线程支持。thread 模块提供了基本的线程和锁定支持,在Python3 中该模块被重命名为_thread;threading 模块提供了更高级别、功能更全面的线程管理。使用Queue 模块,用户可以创建一个队列数据结构,用于在多线程之间进行共享。

    多进程 

    利用Process来创建子进程

    可以使用multiprocessing模块中的Process来创建子进程,该模块还有更高级的封装,例如批量启动进程的进程池(Pool)、用于进程间通信的队列(Queue)和管道(Pipe)等。

    # -*- coding:utf-8 -*-
    from multiprocessing import Process
    from time import ctime, sleep
    def loop(nloop, nsec):
        print("start loop", nloop, "at:", ctime())
        sleep(nsec)
        print("loop", nloop, "done at:", ctime())
    
    
    if __name__=="__main__":
        p1 = Process(target=loop, args=(1, 4))
        p2 = Process(target=loop, args=(2, 3))
        p1.start()
        p2.start()
        p1.join()
        p2.join()
        print("finished")

    利用进程池

    Pool

    Pool是用于批量启动进程的进程池,我们可以使用它来启动多进程

    # -*- coding:utf-8 -*-
    from multiprocessing import Pool
    from time import ctime, sleep
    def loop(nloop, nsec):
        print("start loop", nloop, "at:", ctime())
        sleep(nsec)
        print("loop", nloop, "done at:", ctime())
    
    
    if __name__=="__main__":
        pool = Pool(processes=3)
        for i, j in zip([1,2],[4,3]):
            # 维持执行的进程总数为processes,当一个进程执行完毕后会添加新的进程进去
            pool.apply_async(loop, args=(i, j))      
        pool.close()
        # 调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束
        pool.join()  

    ProcessPoolExecutor

    从Python3.2开始,标准库 concurrent.futures 模块提供了ProcessPoolExecutor (进程池)供我们使用

    # -*- coding:utf-8 -*-
    from concurrent.futures import ProcessPoolExecutor
    from time import ctime, sleep
    def loop(nloop, nsec):
        print("start loop", nloop, "at:", ctime())
        sleep(nsec)
        print("loop", nloop, "done at:", ctime())
    
    if __name__=="__main__":
        with ProcessPoolExecutor(max_workers=3) as executor:
            all_task = [executor.submit(loop, i, j) for i, j in zip([1,2],[4,3])]

    多线程

    利用Thread创建子线程

    # -*- coding:utf-8 -*-
    import threading
    from time import sleep, ctime, time
    
    def loop(nloop, nsec):
        print("start loop", nloop, "at:", ctime())
        sleep(nsec)
        print("loop", nloop, "done at:", ctime())
    
    def main():
        threads = []
        for i, j in zip([1,2],[4,3]):
            t = threading.Thread(target=loop, args=(i, j))
            threads.append(t)
        # 线程开始执行
        for t in threads:
            t.start()
        # 等待所有线程执行完成
        for t in threads:
            t.join()
    
    if __name__ == "__main__":
        start = time()
        main()    
        print("time: ", time()-start)

    当所有线程都分配完成之后,通过调用每个线程的start()方法让它们开始执行,而不是在这之前就会执行。join()方法将等待线程结束,或者在提供了超时时间的情况下,达到超时时间。相比于管理一组锁(分配、获取、释放、检查锁状态等)而言,使用join()方法要比等待锁释放的无限循环更加清晰(这也是这种锁又称为自旋锁的原因)。

    对于 join()方法而言,其另一个重要方面是其实它根本不需要调用。一旦线程启动,它们就会一直执行,直到给定的函数完成后退出。如果主线程还有其他事情要去做,而不是等待这些线程完成(例如其他处理或者等待新的客户端请求),就可以不调用join()。join()方法只有在你需要等待线程完成的时候才是有用的。

    我们可以创建一个类继承threading.Thead,让这个类更加通用,而不只是针对loop()函数,如果我们有别的函数也可以用这个类来使用多线程。我们需要覆写Thread的__init__()和run()方法,或者调用父类的__init__()然后覆写run()方法。

    Python官方文档:https://docs.python.org/3/library/threading.html#thread-objects

    # -*- coding:utf-8 -*-
    import threading
    from time import sleep, ctime
    class MyThread(threading.Thread):
        def __init__(self, func, args, name=""):
            threading.Thread.__init__(self)
            self.name = name
            self.func = func
            self.args = args
        def run(self):
            self.func(*self.args)
    
    def loop(nloop, nsec):
        print("start loop", nloop, "at:", ctime())
        sleep(nsec)
        print("loop", nloop, "done at:", ctime())
    
    def main():
        print("starting at:", ctime())
        threads = []
        for i, j in zip([1,2],[4,3]):
            t = MyThread(loop, args=(i, j), name=loop.__name__)
            threads.append(t)
            
        # 线程开始执行
        for t in threads:
            t.start()
    
        # 等待所有线程执行完成
        for t in threads:
            t.join()
        
        print("all DONE at:", ctime())
    
    if __name__ == "__main__":
        main()

    利用线程池

    ThreadPool

    # -*- coding:utf-8 -*-
    from multiprocessing.dummy import Pool as ThreadPool
    from time import ctime, sleep
    def loop(nloop, nsec):
        print("start loop", nloop, "at:", ctime())
        sleep(nsec)
        print("loop", nloop, "done at:", ctime())
    
    
    if __name__=="__main__":
        pool = ThreadPool(processes=3)
        for i, j in zip([1,2],[4,3]):
            pool.apply_async(loop, args=(i, j))      
        pool.close()
        pool.join()  

    ThreadPoolExecutor

    从Python3.2开始,标准库 concurrent.futures 模块提供了ThreadPoolExecutor (线程池)供我们使用

    # -*- coding:utf-8 -*-
    from concurrent.futures import ThreadPoolExecutor
    from time import ctime, sleep
    def loop(nloop, nsec):
        print("start loop", nloop, "at:", ctime())
        sleep(nsec)
        print("loop", nloop, "done at:", ctime())
    
    if __name__=="__main__":
        with ThreadPoolExecutor(max_workers=3) as executor:
            all_task = [executor.submit(loop, i, j) for i, j in zip([1,2],[4,3])]

    协程

    协程,英文Coroutines,是一种比线程更加轻量级的存在。正如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程。最重要的是,协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行)。

    这样带来的好处就是性能得到了很大的提升,不会像线程切换那样消耗资源。

     

    引入

    带有yield的函数不再是普通函数,而是生成器。send可以把一个函数的结果传给另外一个函数,以此实现单线程内程序之间的切换。

    # -*- coding:utf-8 -*-
    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) # 等价于next(c)
        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)

    这里,produce先用 c.send(None) 启动生成器,consumer() 执行到 yield r 便停下来,并将r返回 给调用它的函数(比如next()或send());这时候consumer()被挂起,produce继续执行,当运行到 r=c.send(n) 时又让consumer()执行 ;此时consumer()将r赋值给n,并继续往下运行,执行print()函数,并将 '200 OK' 赋值给 r ;之后进入下一个while循环,又到了 yield r ,这时就跟前面一样了,停下了将r返回给调用它的函数,这时produce()里 r=c.send(n);不断重复上面,直到循环结束,c.close()关闭生成器。

    asyncio

    asyncio是Python 3.4版本引入的标准库,直接内置了对异步IO的支持。

    asyncio的编程模型就是一个消息循环。我们从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步IO。

    # -*- coding:utf-8 -*-
    import asyncio
    
    @asyncio.coroutine
    def hello():
        print("Hello world!")
        # 异步调用asyncio.sleep(1):
        r = yield from asyncio.sleep(1)
        print("Hello again!")
    
    # 获取EventLoop:
    loop = asyncio.get_event_loop()
    # 执行coroutine
    loop.run_until_complete(hello())
    loop.close()

    @asyncio.coroutine把一个生成器标记为coroutine类型,然后,我们就把这个coroutine扔到EventLoop中执行。

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

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

    我们用asyncio的异步网络连接来获取sina、sohu和163的网站首页:

    import asyncio
    
    @asyncio.coroutine
    def wget(host):
        print('wget %s...' % host)
        connect = asyncio.open_connection(host, 80)
        reader, writer = yield from connect
        header = 'GET / HTTP/1.0
    Host: %s
    
    ' % host
        writer.write(header.encode('utf-8'))
        yield from writer.drain()
        while True:
            line = yield from reader.readline()
            if line == b'
    ':
                break
            print('%s header > %s' % (host, line.decode('utf-8').rstrip()))
        # Ignore the body, close the socket
        writer.close()
    
    loop = asyncio.get_event_loop()
    tasks = [wget(host) for host in ['www.sina.com.cn', 'www.sohu.com', 'www.163.com']]
    loop.run_until_complete(asyncio.wait(tasks))
    loop.close()

    async/await

    从Python 3.5开始引入了新的语法asyncawait,可以让代码更简洁易读。asyncio是用来编写并发的代码库。 

    # -*- coding:utf-8 -*-
    import asyncio
    
    async def slow_operation(n):
        await asyncio.sleep(1)
        print("Slow operation {} completed".format(n))
    
    async def main():
        await asyncio.wait([
            slow_operation(1),
            slow_operation(2),
            slow_operation(3),
        ])
    
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

     再看一个例子,这里的aiohttp实现了HTTP客户端和HTTP服务器的功能,对异步操作提供了非常好的支持,有兴趣可以阅读它的官方文档

    import asyncio
    import aiohttp
    
    async def download(url):
        print('Fetch:', url)
        async with aiohttp.ClientSession() as session:
            async with session.get(url) as resp:
                print(url, '--->', resp.status)
                print(url, '--->', resp.cookies)
                print('
    
    ', await resp.text())
    
    def main():
        loop = asyncio.get_event_loop()
        urls = [
            'https://www.baidu.com',
            'http://www.sohu.com/',
            'http://www.sina.com.cn/',
            'https://www.taobao.com/',
            'https://www.jd.com/'
        ]
        tasks = [download(url) for url in urls]
        loop.run_until_complete(asyncio.wait(tasks))
        loop.close()
    
    if __name__ == '__main__':
        main()

    参考资料

    https://www.cnblogs.com/friendwrite/articles/10414273.html

    https://www.jianshu.com/p/a69dec87e646

    https://www.cnblogs.com/sui776265233/p/9325996.html

    https://www.liaoxuefeng.com/wiki/1016959663602400/1017968846697824

    《Python核心编程》

    https://www.jianshu.com/p/7be32bf906fb

    https://github.com/jackfrued/Python-100-Days/blob/master/Day66-75/69.%E5%B9%B6%E5%8F%91%E4%B8%8B%E8%BD%BD.md

    https://docs.python.org/zh-cn/3/library/asyncio.html#module-asyncio

  • 相关阅读:
    BZOJ 4316: 小C的独立集 (仙人掌,树形DP)
    LOJ #2587. 「APIO2018」铁人两项 (圆方树,树形DP)
    BZOJ 5329: [Sdoi2018]战略游戏 (圆方树,树链的并)
    CF487E Tourists (圆方树,LCT)
    BZOJ 4873: [Shoi2017]寿司餐厅 最大权闭合图
    【转】python文件打开方式详解——a、a+、r+、w+区别
    【转】使用git将项目上传到github(最简单方法)
    整数型数组组合成字符串
    【转】浏览器中输入url后发生了什么
    去除列表中重复的元素
  • 原文地址:https://www.cnblogs.com/dogecheng/p/11439912.html
Copyright © 2011-2022 走看看