在Python中为多处理共享内存的更好方法是什么?

时间:2015-11-05 01:57:07

标签: python multithreading multiprocessing shared-memory python-multiprocessing

我已经解决了这个问题一个星期了,而且它变得非常令人沮丧,因为每次我实现一个更简单但相似的我需要做的比例示例时,事实证明多处理会捏造它。它处理共享内存的方式令我困惑,因为它非常有限,它可能会很快变得无用。

所以我的问题的基本描述是我需要创建一个在一些参数中传递的进程来打开一个图像并创建大小为60x40的大约20K补丁。这些补丁一次保存到列表2中,需要返回到主线程,然后由GPU上运行的其他2个并发进程再次处理。

流程和工作流程以及所有大部分时间都需要处理的事情,我现在需要的是最容易被认为是最困难的部分。我无法保存并将带有20K补丁的列表返回到主线程。

第一个问题是因为我将这些补丁保存为PIL图像。然后我发现添加到Queue对象的所有数据都必须被pickle。 第二个问题是我然后将补丁转换为每个60x40的数组并将它们保存到列表中。而现在仍然没有效果?显然,当您调用queue_obj.get()程序挂起时,队列可以保存有限数量的数据。

我已经尝试了很多其他的东西,而且我尝试的每一件新东西都不起作用,所以我想知道是否有人有一个库的其他建议我可以用它来共享对象而没有所有模糊的东西?

以下是我所看到的一种示例实现。请记住,这种方法非常好,但完全实现并不是很好。我确实有代码打印信息性消息,以确保保存的数据具有完全相同的形状和一切,但由于某种原因它不起作用。在完整实现中,独立过程成功完成,但在q.get()处冻结。

from PIL import Image
from multiprocessing import Queue, Process
import StringIO
import numpy

img = Image.open("/path/to/image.jpg")
q = Queue()
q2 = Queue()
#
#
# MAX Individual Queue limit for 60x40 images in BW is 31,466.
# Multiple individual Queues can be filled to the max limit of 31,466.
# A single Queue can only take up to 31,466, even if split up in different puts.
def rz(patch, qn1, qn2):
    totalPatchCount = 20000
    channels = 1
    patch = patch.resize((60,40), Image.ANTIALIAS)
    patch = patch.convert('L')
    # ImgArray = numpy.asarray(im, dtype=numpy.float32)
    list_im_arr = []
    # ----Create a 4D Array
    # returnImageArray = numpy.zeros(shape=(totalPatchCount, channels, 40, 60))
    imgArray = numpy.asarray(patch, dtype=numpy.float32)
    imgArray = imgArray[numpy.newaxis, ...]
    # ----End 4D array
    # list_im_arr2 = []
    for i in xrange(totalPatchCount):
        # returnImageArray[i] = imgArray
        list_im_arr.append(imgArray)
    qn1.put(list_im_arr)
    qn1.cancel_join_thread()
    # qn2.cancel_join_thread()
    print "PROGRAM Done"

# rz(img,q,q2)
# l = q.get()

#
p = Process(target=rz,args=(img, q, q2,))
p.start()
p.join()
#
# # l = []
# # for i in xrange(1000): l.append(q.get())
#
imdata = q.get()

2 个答案:

答案 0 :(得分:6)

队列用于进程之间的通信。在你的情况下,你真的没有这种沟通。您可以简单地让流程返回结果,并使用.get()方法来收集它们。 (请务必添加if __name__ == "main":,请参阅programming guideline

from PIL import Image
from multiprocessing import Pool, Lock
import numpy

img = Image.open("/path/to/image.jpg")

def rz():
    totalPatchCount = 20000
    imgArray = numpy.asarray(patch, dtype=numpy.float32)
    list_im_arr = [imgArray] * totalPatchCount  # A more elegant way than a for loop
    return list_im_arr

if __name__ == '__main__':  
    # patch = img....  Your code to get generate patch here
    patch = patch.resize((60,40), Image.ANTIALIAS)
    patch = patch.convert('L')

    pool = Pool(2)
    imdata = [pool.apply_async(rz).get() for x in range(2)]
    pool.close()
    pool.join()

现在,根据这个post的第一个答案,多处理只传递可选择的对象。在多处理中,酸洗可能是不可避免的,因为进程不共享内存。他们根本不住在同一个宇宙中。 (他们在第一次产生时会继承记忆,但是他们无法触及他们自己的宇宙)。 PIL图像对象本身不可拾取。您可以通过仅提取存储在其中的图像数据来使其可选择,如建议的post

由于您的问题主要是I / O绑定,您还可以尝试多线程。它可能会更快达到您的目的。线程共享所有内容,因此不需要酸洗。如果您使用的是python 3,ThreadPoolExecutor是一个很棒的工具。对于Python 2,您可以使用ThreadPool。为了实现更高的效率,您必须重新安排您的工作方式,您希望分解流程并让不同的线程完成工作。

from PIL import Image
from multiprocessing.pool import ThreadPool
from multiprocessing import Lock
import numpy

img = Image.open("/path/to/image.jpg")
lock = Lock():
totalPatchCount = 20000

def rz(x):
    patch = ...
    return patch

pool = ThreadPool(8)
imdata = [pool.map(rz, range(totalPatchCount)) for i in range(2)]
pool.close()
pool.join()

答案 1 :(得分:1)

你说"显然队列的数据量有限,否则当你调用queue_obj.get()时程序会挂起。"

你在那里是对是错。 Queue将保留的信息量有限且不会耗尽。问题是当你这样做时:

qn1.put(list_im_arr)
qn1.cancel_join_thread()

它安排与底层管道的通信(由线程处理)。 qn1.cancel_join_thread()然后说"but it's cool if we exit without the scheduled put completing",当然,几微秒后,工作函数退出,Process退出(无需等待填充管道的线程实际执行此操作;充其量它可能已经发送了对象的初始字节,但是任何不适合PIPE_BUF的东西几乎肯定会被丢弃;你需要一些惊人的竞争条件来获得任何东西所有,更不用说整个大型物体了)。所以稍后,当你这样做时:

imdata = q.get()

(现已退出)Process实际上没有发送任何内容。当您致电q.get()时,它正在等待从未实际传输的数据。

另一个答案是正确的,在计算和传达单个值的情况下,Queue s是过度的。但是如果您要使用它们,则需要正确使用它们。修复将是:

  1. 移除对qn1.cancel_join_thread()的来电,以便Process不会退出,直到数据通过管道传输。
  2. 重拨您的电话以避免死锁
  3. 重新安排就是这样:

    p = Process(target=rz,args=(img, q, q2,))
    p.start()
    
    imdata = q.get()
    p.join()
    

    p.join()之后移动q.get();如果你首先尝试join,你的主进程将等待子进程退出,并且子进程将在它退出之前等待队列被消耗(这可能在{{1}管道在主进程中被一个线程耗尽,但最好不要依赖于那样的实现细节;只要Queue,这个表单无论实现细节如何都是正确的。 s和put匹配)。