热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

python模块multiprocess多进程

文章目录简介进程间的数据共享使用Array共享数据使用Manager共享数据使用queues的Queue类共享数据进程锁进程池Pool类简介multiprocess提供了Proc

文章目录

    • 简介
    • 进程间的数据共享
      • 使用Array共享数据
      • 使用Manager共享数据
      • 使用queues的Queue类共享数据
      • 进程锁
      • 进程池Pool类




在这里插入图片描述

简介

multiprocess提供了Process类,实现进程相关的功能。但是它基于fork机制,因此不被windows平台支持。想要在windows中运行,必须使用if __name__ == '__main__':的方式,显然这只能用于调试和学习,不能用于实际环境。

另外,在multiprocess中你既可以import大写的Process,也可以import小写的process,这两者是完全不同的东西。这种情况在Python中很多,请一定要小心和注意。

下面是一个简单的多进程例子,Process类的用法和Thread类几乎一模一样。

import os
import multiprocessingdef foo(i):# 同样的参数传递方法print("这里是 ", multiprocessing.current_process().name)print('模块名称:', __name__)print('父进程 id:', os.getppid()) # 获取父进程idprint('当前子进程 id:', os.getpid()) # 获取自己的进程idprint('------------------------')if __name__ == '__main__':for i in range(5):p = multiprocessing.Process(target=foo, args=(i,))p.start()

运行结果:

这里是 Process-2
模块名称: __mp_main__
父进程 id: 880
当前子进程 id: 5260
--------------
这里是 Process-3
模块名称: __mp_main__
父进程 id: 880
当前子进程 id: 4912
--------------
这里是 Process-4
模块名称: __mp_main__
父进程 id: 880
当前子进程 id: 5176
--------------
这里是 Process-1
模块名称: __mp_main__
父进程 id: 880
当前子进程 id: 5380
--------------
这里是 Process-5
模块名称: __mp_main__
父进程 id: 880
当前子进程 id: 3520
--------------

进程间的数据共享

在Linux中,每个子进程的数据都是由父进程提供的,每启动一个子进程就从父进程克隆一份数据。

创建一个进程需要非常大的开销,每个进程都有自己独立的数据空间,不同进程之间通常是不能共享数据的,要想共享数据,一般通过中间件来实现。

下面我们尝试用一个全局列表来实现进程间的数据共享:

from multiprocessing import Processlis = []def foo(i):lis.append(i)print("This is Process ", i," and lis is ", lis, " and lis.address is ", id(lis))if __name__ == '__main__':for i in range(5):p = Process(target=foo, args=(i,))p.start()print("The end of list_1:", lis)

运行结果:

The end of list_1: []
This is Process 2 and lis is [2] and lis.address is 40356744
This is Process 1 and lis is [1] and lis.address is 40291208
This is Process 0 and lis is [0] and lis.address is 40291208
This is Process 3 and lis is [3] and lis.address is 40225672
This is Process 4 and lis is [4] and lis.address is 40291208

可以看到,全局列表lis没有起到任何作用,在主进程和子进程中,lis指向内存中不同的列表。

想要在进程之间进行数据共享可以使用QueuesArrayManager这三个multiprocess模块提供的类。

使用Array共享数据

对于Array数组类,括号内的“i”表示它内部的元素全部是int类型,而不是指字符“i”,数组内的元素可以预先指定,也可以只指定数组的长度。Array类在实例化的时候必须指定数组的数据类型和数组的大小,类似temp = Array('i', 5)。对于数据类型有下面的对应关系:

'c': ctypes.c_char, 'u': ctypes.c_wchar,
'b': ctypes.c_byte, 'B': ctypes.c_ubyte,
'h': ctypes.c_short, 'H': ctypes.c_ushort,
'i': ctypes.c_int, 'I': ctypes.c_uint,
'l': ctypes.c_long, 'L': ctypes.c_ulong,
'f': ctypes.c_float, 'd': ctypes.c_double

看下面的例子:

from multiprocessing import Process
from multiprocessing import Arraydef func(i,temp):temp[0] += 100print("进程%s " % i, ' 修改数组第一个元素后----->', temp[0])if __name__ == '__main__':temp = Array('i', [1, 2, 3, 4])for i in range(10):p = Process(target=func, args=(i, temp))p.start()

运行结果:

进程2 修改数组第一个元素后-----> 101
进程4 修改数组第一个元素后-----> 201
进程5 修改数组第一个元素后-----> 301
进程3 修改数组第一个元素后-----> 401
进程1 修改数组第一个元素后-----> 501
进程6 修改数组第一个元素后-----> 601
进程9 修改数组第一个元素后-----> 701
进程8 修改数组第一个元素后-----> 801
进程0 修改数组第一个元素后-----> 901
进程7 修改数组第一个元素后-----> 1001

使用Manager共享数据

通过Manager类也可以实现进程间数据的共享。Manager()返回的manager对象提供一个服务进程,使得其他进程可以通过代理的方式操作Python对象。manager对象支持 list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore, Condition, Event, Barrier, Queue, Value ,Array等多种格式。

from multiprocessing import Process
from multiprocessing import Managerdef func(i, dic):dic["num"] = 100+iprint(dic.items())if __name__ == '__main__':dic = Manager().dict()for i in range(10):p = Process(target=func, args=(i, dic))p.start()p.join()

运行结果:

[('num', 100)]
[('num', 101)]
[('num', 102)]
[('num', 103)]
[('num', 104)]
[('num', 105)]
[('num', 106)]
[('num', 107)]
[('num', 108)]
[('num', 109)]

使用queues的Queue类共享数据

multiprocessing是一个包,它内部又一个queues模块,提供了一个Queue队列类,可以实现进程间的数据共享,如下例所示:

import multiprocessing
from multiprocessing import Process
from multiprocessing import queuesdef func(i, q):ret = q.get()print("进程%s从队列里获取了一个%s,然后又向队列里放入了一个%s" % (i, ret, i))q.put(i)if __name__ == "__main__":lis = queues.Queue(20, ctx=multiprocessing)lis.put(0)for i in range(10):p = Process(target=func, args=(i, lis,))p.start()

运行结果:

进程1从队列里获取了一个0,然后又向队列里放入了一个1
进程4从队列里获取了一个1,然后又向队列里放入了一个4
进程2从队列里获取了一个4,然后又向队列里放入了一个2
进程6从队列里获取了一个2,然后又向队列里放入了一个6
进程0从队列里获取了一个6,然后又向队列里放入了一个0
进程5从队列里获取了一个0,然后又向队列里放入了一个5
进程9从队列里获取了一个5,然后又向队列里放入了一个9
进程7从队列里获取了一个9,然后又向队列里放入了一个7
进程3从队列里获取了一个7,然后又向队列里放入了一个3
进程8从队列里获取了一个3,然后又向队列里放入了一个8

关于queueQueue,在Python库中非常频繁的出现,很容易就搞混淆了。甚至是multiprocessing自己还有一个Queue类(大写的Q),一样能实现queues.Queue的功能,导入方式是from multiprocessing import Queue

进程锁

为了防止和多线程一样的出现数据抢夺和脏数据的问题,同样需要设置进程锁。与threading类似,在multiprocessing里也有同名的锁类RLock,Lock,Event,ConditionSemaphore,连用法都是一样样的,这一点非常友好!

from multiprocessing import Process
from multiprocessing import Array
from multiprocessing import RLock, Lock, Event, Condition, Semaphore
import timedef func(i,lis,lc):lc.acquire()lis[0] = lis[0] - 1time.sleep(1)print('say hi', lis[0])lc.release()if __name__ == "__main__":array = Array('i', 1)array[0] = 10lock = RLock()for i in range(10):p = Process(target=func, args=(i, array, lock))p.start()

运行结果:

say hi 9
say hi 8
say hi 7
say hi 6
say hi 5
say hi 4
say hi 3
say hi 2
say hi 1
say hi 0

进程池Pool类

进程启动的开销比较大,过多的创建新进程会消耗大量的内存空间。仿照线程池的做法,我们可以使用进程池控制内存开销。

比较幸运的是,Python给我们内置了一个进程池,不需要像线程池那样要自己写,你只需要简单的from multiprocessing import Pool导入就行。进程池内部维护了一个进程序列,需要时就去进程池中拿取一个进程,如果进程池序列中没有可供使用的进程,那么程序就会等待,直到进程池中有可用进程为止。

进程池中常用的方法:

  • apply() 同步执行(串行)
  • apply_async() 异步执行(并行)
  • terminate() 立刻关闭进程池
  • join() 主进程等待所有子进程执行完毕。必须在close或terminate()之后。
  • close() 等待所有进程结束后,才关闭进程池。

from multiprocessing import Pool
import timedef func(args):time.sleep(1)print("正在执行进程 ", args)if __name__ == '__main__':p = Pool(5) # 创建一个包含5个进程的进程池for i in range(30):p.apply_async(func=func, args=(i,))p.close() # 等子进程执行完毕后关闭进程池# time.sleep(2)# p.terminate() # 立刻关闭进程池p.join()

参考:

  • 多进程multiprocess
  • python 进程、线程、协程详解
  • Python - Multithreaded Programming

推荐阅读
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 本文介绍了机器学习手册中关于日期和时区操作的重要性以及其在实际应用中的作用。文章以一个故事为背景,描述了学童们面对老先生的教导时的反应,以及上官如在这个过程中的表现。同时,文章也提到了顾慎为对上官如的恨意以及他们之间的矛盾源于早年的结局。最后,文章强调了日期和时区操作在机器学习中的重要性,并指出了其在实际应用中的作用和意义。 ... [详细]
  • LeetCode笔记:剑指Offer 41. 数据流中的中位数(Java、堆、优先队列、知识点)
    本文介绍了LeetCode剑指Offer 41题的解题思路和代码实现,主要涉及了Java中的优先队列和堆排序的知识点。优先队列是Queue接口的实现,可以对其中的元素进行排序,采用小顶堆的方式进行排序。本文还介绍了Java中queue的offer、poll、add、remove、element、peek等方法的区别和用法。 ... [详细]
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 本文探讨了C语言中指针的应用与价值,指针在C语言中具有灵活性和可变性,通过指针可以操作系统内存和控制外部I/O端口。文章介绍了指针变量和指针的指向变量的含义和用法,以及判断变量数据类型和指向变量或成员变量的类型的方法。还讨论了指针访问数组元素和下标法数组元素的等价关系,以及指针作为函数参数可以改变主调函数变量的值的特点。此外,文章还提到了指针在动态存储分配、链表创建和相关操作中的应用,以及类成员指针与外部变量的区分方法。通过本文的阐述,读者可以更好地理解和应用C语言中的指针。 ... [详细]
  • 如何在服务器主机上实现文件共享的方法和工具
    本文介绍了在服务器主机上实现文件共享的方法和工具,包括Linux主机和Windows主机的文件传输方式,Web运维和FTP/SFTP客户端运维两种方式,以及使用WinSCP工具将文件上传至Linux云服务器的操作方法。此外,还介绍了在迁移过程中需要安装迁移Agent并输入目的端服务器所在华为云的AK/SK,以及主机迁移服务会收集的源端服务器信息。 ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
author-avatar
巧干篆书_618
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有