• 多线程:Python中提供了类并辅以LockConditionEventSemaphoreBarrier。Python中有GIL来防止多个线程同时执行本地字节码,这个锁对于CPython是必须的,因为CPython的内存管理并不是线程安全的,因为GIL的存在多线程并不能发挥CPU的多核特性。

      ```Python “”” 面试题:进程和线程的区别和联系? 进程 - 操作系统分配内存的基本单位 - 一个进程可以包含一个或多个线程 线程 - 操作系统分配CPU的基本单位 并发编程(concurrent programming)

      1. 提升执行性能 - 让程序中没有因果关系的部分可以并发的执行
      2. 改善用户体验 - 让耗时间的操作不会造成程序的假死 “”” import glob import os import threading

      from PIL import Image

      PREFIX = ‘thumbnails’

    def generatethumbnail(infile, size, format=’PNG’): “””生成指定图片文件的缩略图””” file, ext = os.path.splitext(infile) file = file[file.rfind(‘/‘) + 1:] outfile = f’{PREFIX}/{file}{size[0]}_{size[1]}.{ext}’ img = Image.open(infile) img.thumbnail(size, Image.ANTIALIAS) img.save(outfile, format)

    def main(): “””主函数””” if not os.path.exists(PREFIX): os.mkdir(PREFIX) for infile in glob.glob(‘images/*.png’): for size in (32, 64, 128):

    1. 多个线程竞争资源的情况。
    2. ```Python
    3. """
    4. 多线程程序如果没有竞争资源处理起来通常也比较简单
    5. 当多个线程竞争临界资源的时候如果缺乏必要的保护措施就会导致数据错乱
    6. 说明:临界资源就是被多个线程竞争的资源
    7. """
    8. import time
    9. import threading
    10. from concurrent.futures import ThreadPoolExecutor
    11. class Account(object):
    12. """银行账户"""
    13. def __init__(self):
    14. self.balance = 0.0
    15. self.lock = threading.Lock()
    16. def deposit(self, money):
    17. # 通过锁保护临界资源
    18. with self.lock:
    19. new_balance = self.balance + money
    20. time.sleep(0.001)
    21. self.balance = new_balance
    22. def main():
    23. """主函数"""
    24. account = Account()
    25. # 创建线程池
    26. pool = ThreadPoolExecutor(max_workers=10)
    27. futures = []
    28. for _ in range(100):
    29. future = pool.submit(account.deposit, 1)
    30. futures.append(future)
    31. # 关闭线程池
    32. for future in futures:
    33. future.result()
    34. print(account.balance)
    35. if __name__ == '__main__':

    修改上面的程序,启动5个线程向账户中存钱,5个线程从账户中取钱,取钱时如果余额不足就暂停线程进行等待。为了达到上述目标,需要对存钱和取钱的线程进行调度,在余额不足时取钱的线程暂停并释放锁,而存钱的线程将钱存入后要通知取钱的线程,使其从暂停状态被唤醒。可以使用threading模块的Condition来实现线程调度,该对象也是基于锁来创建的,代码如下所示:

    • 多进程:多进程可以有效的解决GIL的问题,实现多进程主要的类是Process,其他辅助的类跟threading模块中的类似,进程间共享数据可以使用管道、套接字等,在multiprocessing模块中有一个Queue类,它基于管道和锁机制提供了多个进程共享的队列。下面是官方文档上关于多进程和进程池的一个示例。

      ```Python “”” 多进程和进程池的使用 多线程因为GIL的存在不能够发挥CPU的多核特性 对于计算密集型任务应该考虑使用多进程 time python3 example22.py real 0m11.512s user 0m39.319s sys 0m0.169s 使用多进程后实际执行时间为11.512秒,而用户时间39.319秒约为实际执行时间的4倍 这就证明我们的程序通过多进程使用了CPU的多核特性,而且这台计算机配置了4核的CPU “”” import concurrent.futures import math

      PRIMES = [

      1. 1116281,
      2. 1297337,
      3. 104395303,
      4. 472882027,
      5. 533000389,
      6. 817504243,
      7. 982451653,
      8. 112272535095293,
      9. 112582705942171,
      10. 112272535095293,
      11. 115280095190773,
      12. 115797848077099,
      13. 1099726899285419

      ] * 5

    def is_prime(n): “””判断素数””” if n % 2 == 0: return False

    if name == ‘main‘: main()

    1. > **重点**:**多线程和多进程的比较**。
    2. >
    3. > 以下情况需要使用多线程:
    4. >
    5. > 1. 程序需要维护许多共享的状态(尤其是可变状态),Python中的列表、字典、集合都是线程安全的,所以使用线程而不是进程维护共享状态的代价相对较小。
    6. > 2. 程序会花费大量时间在I/O操作上,没有太多并行计算的需求且不需占用太多的内存。
    7. >
    8. > 以下情况需要使用多进程:
    9. >
    10. > 1. 程序执行计算密集型任务(如:字节码操作、数据处理、科学计算)。
    11. > 2. 程序的输入可以并行的分成块,并且可以将运算结果合并。
    12. > 3. 程序在内存使用方面没有任何限制且不强依赖于I/O操作(如:读写文件、套接字等)。
    13. - 异步处理:从调度程序的任务队列中挑选任务,该调度程序以交叉的形式执行这些任务,我们并不能保证任务将以某种顺序去执行,因为执行顺序取决于队列中的一项任务是否愿意将CPU处理时间让位给另一项任务。异步任务通常通过多任务协作处理的方式来实现,由于执行时间和顺序的不确定,因此需要通过回调式编程或者`future`对象来获取任务执行的结果。Python 3通过`asyncio`模块和`await``async`关键字(在Python 3.7中正式被列为关键字)来支持异步处理。
    14. ```Python
    15. """
    16. 异步I/O - async / await
    17. """
    18. import asyncio
    19. def num_generator(m, n):
    20. """指定范围的数字生成器"""
    21. yield from range(m, n + 1)
    22. async def prime_filter(m, n):
    23. """素数过滤器"""
    24. primes = []
    25. for i in num_generator(m, n):
    26. flag = True
    27. if i % j == 0:
    28. flag = False
    29. break
    30. if flag:
    31. print('Prime =>', i)
    32. primes.append(i)
    33. await asyncio.sleep(0.001)
    34. return tuple(primes)
    35. async def square_mapper(m, n):
    36. """平方映射器"""
    37. squares = []
    38. for i in num_generator(m, n):
    39. print('Square =>', i * i)
    40. squares.append(i * i)
    41. await asyncio.sleep(0.001)
    42. return squares
    43. def main():
    44. """主函数"""
    45. loop = asyncio.get_event_loop()
    46. future = asyncio.gather(prime_filter(2, 100), square_mapper(1, 100))
    47. future.add_done_callback(lambda x: print(x.result()))
    48. loop.run_until_complete(future)
    49. loop.close()
    50. if __name__ == '__main__':

    Python中有一个名为aiohttp的三方库,它提供了异步的HTTP客户端和服务器,这个三方库可以跟asyncio模块一起工作,并提供了对Future对象的支持。Python 3.6中引入了asyncawait来定义异步执行的函数以及创建异步上下文,在Python 3.7中它们正式成为了关键字。下面的代码异步的从5个URL中获取页面并通过正则表达式的命名捕获组提取了网站的标题。

    重点异步I/O与多进程的比较

    当程序不需要真正的并发性或并行性,而是更多的依赖于异步处理和回调时,asyncio就是一种很好的选择。如果程序中有大量的等待与休眠时,也应该考虑asyncio,它很适合编写没有实时数据处理需求的Web应用服务器。

    Python还有很多用于处理并行任务的三方库,例如:joblibPyMP等。实际开发中,要提升系统的可扩展性和并发性通常有垂直扩展(增加单个节点的处理能力)和水平扩展(将单个节点变成多个节点)两种做法。可以通过消息队列来实现应用程序的解耦合,消息队列相当于是多线程同步队列的扩展版本,不同机器上的应用程序相当于就是线程,而共享的分布式消息队列就是原来程序中的Queue。消息队列(面向消息的中间件)的最流行和最标准化的实现是AMQP(高级消息队列协议),AMQP源于金融行业,提供了排队、路由、可靠传输、安全等功能,最著名的实现包括:Apache的ActiveMQ、RabbitMQ等。