Python多进程并发与多线程并发编程实例总结

本文实例总结了Python多进程并发与多线程并发。分享给大家供大家参考,具体如下:

这里对python支持的几种并发方式进行简单的总结。

Python支持的并发分为多线程并发与多进程并发(异步IO本文不涉及)。概念上来说,多进程并发即运行多个独立的程序,优势在于并发处理的任务都由操作系统管理,不足之处在于程序与各进程之间的通信和数据共享不方便;多线程并发则由程序员管理并发处理的任务,这种并发方式可以方便地在线程间共享数据(前提是不能互斥)。Python对多线程和多进程的支持都比一般编程语言更高级,最小化了需要我们完成的工作。

一.多进程并发

Mark Summerfield指出,对于计算密集型程序,多进程并发优于多线程并发。计算密集型程序指的程序的运行时间大部分消耗在CPU的运算处理过程,而硬盘和内存的读写消耗的时间很短;相对地,IO密集型程序指的则是程序的运行时间大部分消耗在硬盘和内存的读写上,CPU的运算时间很短。

对于多进程并发,python支持两种实现方式,一种是采用进程安全的数据结构:multiprocessing.JoinableQueue,这种数据结构自己管理“加锁”的过程,程序员无需担心“死锁”的问题;python还提供了一种更为优雅而高级的实现方式:采用进程池。下面一一介绍。

1.队列实现——使用multiprocessing.JoinableQueue

multiprocessing是python标准库中支持多进程并发的模块,我们这里采用multiprocessing中的数据结构:JoinableQueue,它本质上仍是一个FIFO的队列,它与一般队列(如queue中的Queue)的区别在于它是多进程安全的,这意味着我们不用担心它的互斥和死锁问题。JoinableQueue主要可以用来存放执行的任务和收集任务的执行结果。举例来看(以下皆省去导入包的过程):

def read(q):
  while True:
    try:
      value = q.get()
      print('Get %s from queue.' % value)
      time.sleep(random.random())
    finally:
      q.task_done()
def main():
  q = multiprocessing.JoinableQueue()
  pw1 = multiprocessing.Process(target=read, args=(q,))
  pw2 = multiprocessing.Process(target=read, args=(q,))
  pw1.daemon = True
  pw2.daemon = True
  pw1.start()
  pw2.start()
  for c in [chr(ord('A')+i) for i in range(26)]:
    q.put(c)
  try:
    q.join()
  except KeyboardInterrupt:
    print("stopped by hand")
if __name__ == '__main__':
  main()

对于windows系统的多进程并发,程序文件里必须含有“入口函数”(如main函数),且结尾处必须调用入口点。例如以if __name__ == '__main__': main()结尾。

在这个最简单的多进程并发例子里,我们用多进程实现将26个字母打印出来。首先定义一个存放任务的JoinableQueue对象,然后实例化两个Process对象(每个对象对应一个子进程),实例化Process对象需要传送target和args参数,target是实现每个任务工作中的具体函数,args是target函数的参数。

pw1.daemon = True
pw2.daemon = True

这两句话将子进程设置为守护进程——主进程结束后随之结束。

pw1.start()
pw2.start()

一旦运行到这两句话,子进程就开始独立于父进程运行了,它会在单独的进程里调用target引用的函数——在这里即read函数,它是一个死循环,将参数q中的数一一读取并打印出来。

value = q.get()

这是多进程并发的要点,q是一个JoinableQueue对象,支持get方法读取第一个元素,如果q中没有元素,进程就会阻塞,直至q中被存入新元素。

因此执行完pw1.start() pw2.start()这两句话后,子进程虽然开始运行了,但很快就堵塞住。

for c in [chr(ord('A')+i) for i in range(26)]:
    q.put(c)

将26个字母依次放入JoinableQueue对象中,这时候两个子进程不再阻塞,开始真正地执行任务。两个子进程都用value = q.get()来读取数据,它们都在修改q对象,而我们并不用担心同步问题,这就是multiProcessing.Joinable数据结构的优势所在——它是多进程安全的,它会自动处理“加锁”的过程。

try:
    q.join()

q.join()方法会查询q中的数据是否已读完——这里指的就是任务是否执行完,如果没有,程序会阻塞住等待q中数据读完才开始继续执行(可以用Ctrl+C强制停止)。

对Windows系统,调用任务管理器应该可以看到有多个子进程在运行。

2.进程池实现——使用concurrent.futures.ProcessPoolExecutor

Python还支持一种更为优雅的多进程并发方式,直接看例子:

def read(q):
    print('Get %s from queue.' % q)
    time.sleep(random.random())
def main():
  futures = set()
  with concurrent.futures.ProcessPoolExecutor() as executor:
    for q in (chr(ord('A')+i) for i in range(26)):
      future = executor.submit(read, q)
      futures.add(future)
  try:
    for future in concurrent.futures.as_completed(futures):
      err = future.exception()
      if err is not None:
        raise err
  except KeyboardInterrupt:
    print("stopped by hand")
if __name__ == '__main__':
  main()

这里我们采用concurrent.futures.ProcessPoolExecutor对象,可以把它想象成一个进程池,子进程往里“填”。我们通过submit方法实例一个Future对象,然后把这里Future对象都填到池——futures里,这里futures是一个set对象。只要进程池里有future,就会开始执行任务。这里的read函数更为简单——只是把一个字符打印并休眠一会而已。

try:
    for future in concurrent.futures.as_completed(futures):

这是等待所有子进程都执行完毕。子进程执行过程中可能抛出异常,err = future.exception()可以收集这些异常,便于后期处理。

可以看出用Future对象处理多进程并发更为简洁,无论是target函数的编写、子进程的启动等等,future对象还可以向使用者汇报其状态,也可以汇报执行结果或执行时的异常。

二.多线程并发

对于IO密集型程序,多线程并发可能要优于多进程并发。因为对于网络通信等IO密集型任务来说,决定程序效率的主要是网络延迟,这时候是使用进程还是线程就没有太大关系了。

1.队列实现——使用queue.Queue

程序与多进程基本一致,只是这里我们不必使用multiProcessing.JoinableQueue对象了,一般的队列(来自queue.Queue)就可以满足要求:

def read(q):
  while True:
    try:
      value = q.get()
      print('Get %s from queue.' % value)
      time.sleep(random.random())
    finally:
      q.task_done()
def main():
  q = queue.Queue()
  pw1 = threading.Thread(target=read, args=(q,))
  pw2 = threading.Thread(target=read, args=(q,))
  pw1.daemon = True
  pw2.daemon = True
  pw1.start()
  pw2.start()
  for c in [chr(ord('A')+i) for i in range(26)]:
    q.put(c)
  try:
    q.join()
  except KeyboardInterrupt:
    print("stopped by hand")
if __name__ == '__main__':
  main()

并且这里我们实例化的是Thread对象,而不是Process对象,程序的其余部分看起来与多进程并没有什么两样。

2. 线程池实现——使用concurrent.futures.ThreadPoolExecutor

直接看例子:

def read(q):
    print('Get %s from queue.' % q)
    time.sleep(random.random())
def main():
  futures = set()
  with concurrent.futures.ThreadPoolExecutor(multiprocessing.cpu_count()*4) as executor:
    for q in (chr(ord('A')+i) for i in range(26)):
      future = executor.submit(read, q)
      futures.add(future)
  try:
    for future in concurrent.futures.as_completed(futures):
      err = future.exception()
      if err is not None:
        raise err
  except KeyboardInterrupt:
    print("stopped by hand")
if __name__ == '__main__':
  main()

用ThreadPoolExecutor与用ProcessPoolExecutor看起来没什么区别,只是改了一下签名而已。

不难看出,不管是使用队列还是使用进/线程池,从多进程转化到多线程是十分容易的——仅仅是修改了几个签名而已。当然内部机制完全不同,只是python的封装非常好,使我们可以不用关心这些细节,这正是python优雅之处。

更多关于Python相关内容感兴趣的读者可查看本站专题:《Python进程与线程操作技巧总结》、《Python Socket编程技巧总结》、《Python数据结构与算法教程》、《Python函数使用技巧总结》、《Python字符串操作技巧汇总》、《Python入门与进阶经典教程》及《Python文件与目录操作技巧汇总》

希望本文所述对大家Python程序设计有所帮助。

您可能感兴趣的文章:

  • python并发编程之多进程、多线程、异步和协程详解
  • Python控制多进程与多线程并发数总结
  • Python 多进程并发操作中进程池Pool的实例
  • Python多进程并发(multiprocessing)用法实例详解
  • python实现多线程的方式及多条命令并发执行
  • 详解Python中的多线程编程
  • Python中多线程thread与threading的实现方法
  • 浅析Python中的多进程与多线程的使用
  • 基python实现多线程网页爬虫
  • python多线程http下载实现示例
(0)

相关推荐

  • python多线程http下载实现示例

    测试平台 Ubuntu 13.04 X86_64 Python 2.7.4 花了将近两个小时, 问题主要刚开始没有想到传一个文件对象到线程里面去, 导致下载下来的文件和源文件MD5不一样,浪费不少时间. 有兴趣的同学可以拿去加上参数,改进下, 也可以加上断点续传. 复制代码 代码如下: # -*- coding: utf-8 -*-# Author: ToughGuy# Email: wj0630@gmail.com# 写这玩意儿是为了初步了解下python的多线程机制# 平时没写注释的习惯,

  • 详解Python中的多线程编程

    一.简介 多线程编程技术可以实现代码并行性,优化处理能力,同时功能的更小划分可以使代码的可重用性更好.Python中threading和Queue模块可以用来实现多线程编程. 二.详解 1.线程和进程        进程(有时被称为重量级进程)是程序的一次执行.每个进程都有自己的地址空间.内存.数据栈以及其它记录其运行轨迹的辅助数据.操作系统管理在其上运行的所有进程,并为这些进程公平地分配时间.进程也可以通过fork和spawn操作来完成其它的任务,不过各个进程有自己的内存空间.数据栈等,所以只

  • Python中多线程thread与threading的实现方法

    学过Python的人应该都知道,Python是支持多线程的,并且是native的线程.本文主要是通过thread和threading这两个模块来实现多线程的. python的thread模块是比较底层的模块,python的threading模块是对thread做了一些包装的,可以更加方便的被使用. 这里需要提一下的是python对线程的支持还不够完善,不能利用多CPU,但是下个版本的python中已经考虑改进这点,让我们拭目以待吧. threading模块里面主要是对一些线程的操作对象化了,创建

  • 浅析Python中的多进程与多线程的使用

    在批评Python的讨论中,常常说起Python多线程是多么的难用.还有人对 global interpreter lock(也被亲切的称为"GIL")指指点点,说它阻碍了Python的多线程程序同时运行.因此,如果你是从其他语言(比如C++或Java)转过来的话,Python线程模块并不会像你想象的那样去运行.必须要说明的是,我们还是可以用Python写出能并发或并行的代码,并且能带来性能的显著提升,只要你能顾及到一些事情.如果你还没看过的话,我建议你看看Eqbal Quran的文章

  • python实现多线程的方式及多条命令并发执行

    一.概念介绍 Thread 是threading模块中最重要的类之一,可以使用它来创建线程.有两种方式来创建线程:一种是通过继承Thread类,重写它的run方法:另一种是创建一个threading.Thread对象,在它的初始化函数(__init__)中将可调用对象作为参数传入. Thread模块是比较底层的模块,Threading模块是对Thread做了一些包装的,可以更加方便的被使用. 另外在工作时,有时需要让多条命令并发的执行, 而不是顺序执行. 二.代码样例 #!/usr/bin/py

  • Python控制多进程与多线程并发数总结

    一.前言 本来写了脚本用于暴力破解密码,可是1秒钟尝试一个密码2220000个密码我的天,想用多线程可是只会一个for全开,难道开2220000个线程吗?只好学习控制线程数了,官方文档不好看,觉得结构不够清晰,网上找很多文章也都不很清晰,只有for全开线程,没有控制线程数的具体说明,最终终于根据多篇文章和官方文档算是搞明白基础的多线程怎么实现法了,怕长时间不用又忘记,找着麻烦就贴这了,跟我一样新手也可以参照参照. 先说进程和线程的区别: 地址空间:进程内的一个执行单元;进程至少有一个线程;它们共

  • 基python实现多线程网页爬虫

    一般来说,使用线程有两种模式, 一种是创建线程要执行的函数, 把这个函数传递进Thread对象里,让它来执行. 另一种是直接从Thread继承,创建一个新的class,把线程执行的代码放到这个新的class里. 实现多线程网页爬虫,采用了多线程和锁机制,实现了广度优先算法的网页爬虫. 先给大家简单介绍下我的实现思路: 对于一个网络爬虫,如果要按广度遍历的方式下载,它是这样的: 1.从给定的入口网址把第一个网页下载下来 2.从第一个网页中提取出所有新的网页地址,放入下载列表中 3.按下载列表中的地

  • Python 多进程并发操作中进程池Pool的实例

    在利用Python进行系统管理的时候,特别是同时操作多个文件目录,或者远程控制多台主机,并行操作可以节约大量的时间.当被操作对象数目不大时,可以直接利用multiprocessing中的Process动态成生多个进程,10几个还好,但如果是上百个,上千个目标,手动的去限制进程数量却又太过繁琐,这时候进程池Pool发挥作用的时候就到了. Pool可以提供指定数量的进程,供用户调用,当有新的请求提交到pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求:但如果池中的进程数已经达到规定

  • Python多进程并发(multiprocessing)用法实例详解

    本文实例讲述了Python多进程并发(multiprocessing)用法.分享给大家供大家参考.具体分析如下: 由于Python设计的限制(我说的是咱们常用的CPython).最多只能用满1个CPU核心. Python提供了非常好用的多进程包multiprocessing,你只需要定义一个函数,Python会替你完成其他所有事情.借助这个包,可以轻松完成从单进程到并发执行的转换. 1.新建单一进程 如果我们新建少量进程,可以如下: import multiprocessing import t

  • python并发编程之多进程、多线程、异步和协程详解

    最近学习python并发,于是对多进程.多线程.异步和协程做了个总结. 一.多线程 多线程就是允许一个进程内存在多个控制权,以便让多个函数同时处于激活状态,从而让多个函数的操作同时运行.即使是单CPU的计算机,也可以通过不停地在不同线程的指令间切换,从而造成多线程同时运行的效果. 多线程相当于一个并发(concunrrency)系统.并发系统一般同时执行多个任务.如果多个任务可以共享资源,特别是同时写入某个变量的时候,就需要解决同步的问题,比如多线程火车售票系统:两个指令,一个指令检查票是否卖完

随机推荐