基本概念

什么叫“多任务”呢?简单地说,就是操作系统可以同时运行多个任务。

无论多线程和多进程,IO的调度更多取决于系统,而协程的方式,调度来自用户,用户可以在函数中yield一个状态。使用协程可以实现高效的并发任务。

并发和并行一直是容易混淆的概念。并发通常指有多个任务需要同时进行,并行则是同一时刻有多个任务执行

进程

进程(process):对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,打开一个Word就启动了一个Word进程。【缺点:进程切换开销大】

多进程:进程数目一般=cpu数量*2+1

多进程方式:from multiprocessing import Pool

线程

线程(thread):有些进程还不止同时干一件事,比如Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。

多线程:线程数目一般=cpu数量*2+1

多线程实现方式:一个Master线程+多个Worker线程

协程

单进程单线程的异步编程模型称为协程, 异步IO是通过将generator标记为coroutine后,和asyncio模块一起实现的。

由一个线程执行,produce和consumer协作完成任务,所以称为“协程”

协程(coroutine):Python对协程的支持是通过generator实现的。。子程序就是协程的一种特例。

协程并发原理:asyncio实现并发,就需要多个协程来完成任务,每当有任务阻塞的时候就await,然后其他协程继续工作。创建多个协程的列表,然后将这些协程注册到事件循环中。

优势:最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。

第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

协程相关

  1. yield: yield不但可以返回一个值,它还可以接收调用者发出的参数,如下斐波拉契数列的推算规则代码所示:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    def fib(max):
        n, a, b = 0, 0, 1
        while n < max:
            yield b
            a, b = b, a + b
            n = n + 1
        return 'done'
    
    def produce(c, max):
    print(c.send(None))
    n = 1
    while n < max:
        print(c.send(None))
        n += 1
    c.close()
    
    produce(fib(6), 6)
    
  2. 使用模块asyncio实现异步io:python3.5开始,async/await新语法替换yield from旧语法

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    # yield from语法可以让我们方便地调用另一个generator
    # yield from=for i in <可迭代对象>: yield i 【把yield from理解为“递归调用Iterable并yield”】
    
    import asyncio
    
    async def wget(host):
        print('wget %s...' % host)
        connect = asyncio.open_connection(host, 80)
        reader, writer = await connect
        header = 'GET / HTTP/1.0\r\nHost: %s\r\n\r\n' % host
        writer.write(header.encode('utf-8'))
        await writer.drain()
        while True:
            line = await reader.readline()
            if line == b'\r\n':
                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()
    
  3. 协程的执行方式是执行-遇到IO中断-继续执行,关键是中断的时候线程去执行其他可以执行的协程去了

  4. 单进程,多线程,多进程和协程比较:协程效率更高

    实现高并发执行的最佳方案:多进程+协程,多进程可以充分利用多核,而协程具有高效率,因此可获得极高的性能。

    图片

    图片

基础知识

  1. fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。【一个进程调用fork()函数后,直接copy fork()后面的代码给子进程】,先执行父进程后执行子进程。【Windows不支持fork调用】

  2. 多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。

  3. 多线程编程,模型复杂,容易发生冲突,必须用锁加以隔离,同时,又要小心死锁的发生。Python解释器由于设计时有GIL全局锁,导致了多线程无法利用多核。多线程的并发在Python中就是一个美丽的梦。Python虽然不能利用多线程实现多核任务,但可以通过多进程实现多核任务。多个Python进程有各自独立的GIL锁,互不影响。

  4. 多进程模式最大的优点就是稳定性高,缺点是创建进程的代价大.

  5. 线程池的注意事项

    虽然线程池是构建多线程应用程序的强大机制,但使用它并不是没有风险的。在使用线程池时需注意线程池大小与性能的关系,注意并发风险、死锁、资源不足和线程泄漏等问题。

    • 线程池大小。多线程应用并非线程越多越好,需要根据系统运行的软硬件环境以及应用本身的特点决定线程池的大小。一般来说,如果代码结构合理的话,线程数目与=CPU数量*2+1【此算法不正确,还需看下官网说明】。如果线程运行时可能出现阻塞现象,可相应增加池的大小;如有必要可采用自适应算法来动态调整线程池的大小,以提高CPU 的有效利用率和系统的整体性能。
    • 并发错误。多线程应用要特别注意并发错误,要从逻辑上保证程序的正确性,注意避免死锁现象的发生。
    • 线程泄漏。这是线程池应用中一个严重的问题,当任务执行完毕而线程没能返回池中就会发生线程泄漏现象。
  6. Python的多线程问题

    python 的GIL规定每个时刻只能有一个线程访问python虚拟机,所以你要用python的多线程来做计算是很不合算的,但是对于IO密集型的应用,例如网络交互来说,python的多线程还是非常给力的。

    如果你是一个计算密集型的任务,非要用python来并行执行的话,有以下几个方法:

    • 使用python的multiprocessing 模块,能够发挥多核的优势。
    • 使用ironPython,但是这个只能在windows下用
    • 使用pypy,这个可以实现真正的多线程。