Python:使用多进程生成图像时卡死
在下面的代码中,我尝试使用多进程生成一个灰度的曼德尔brot集合图像。
为此,我设置了一个队列,并让多个进程生成图像的单独“条带”,然后把它们放入队列中。最后,程序应该把这些条带组合成最终的图像。
可惜的是,在所有进程完成后,程序似乎卡住了,根本没有返回图像。即使我把所有进程也改成保存它们各自的条带,取回这些条带也没有问题。
这是我的代码供参考,虽然不是最简洁的,但我尽量保持可读性并加了注释:
import cv2
import numpy as np
import math
import random
import multiprocessing
import time
path = "C:/Users/tymon/Pictures/Mandelbrot"
# Simple (i.e., not complex LMAO) class allowing representation and manipulation of complex numbers
class complex:
def __init__(self, a, b=0):
self.a = a
self.b = b
def __add__(self, other):
return complex(self.a + other.a, self.b + other.b)
def __sub__(self, other):
return complex(self.a - other.a, self.b - other.b)
def __mul__(self, other):
return complex(
self.a * other.a - self.b * other.b,
self.a * other.b + self.b * other.a)
def __truediv__(self, other):
nominator = other.a * other.a + other.b * other.b
return complex(
(self.a * other.a + self.b * other.b)/nominator,
(self.b * other.a - self.a * other.b)/nominator)
def modulusSqrd(self):
return self.a*self.a + self.b*self.b
def modulus(self):
return math.sqrt(self.modulusSqrd()) # heavy duty calculation
def __str__(self):
sign = "+"
if self.b < 0:
sign = "-"
self.b *= -1
return F"{self.a} {sign} {self.b}i"
# Mandelbrot set is created by recursively applying the simple equation Z_(n+1) = Z_n^2 - c where c is the initial number and Z_0=0 until the magnitude of the number becomes > 2
def simple_equation(c, ite=2):
z = complex(0)
i = 0
while i <= ite and z.modulusSqrd() < 4:
z_next = z*z - c
if math.isinf(z.a) or math.isinf(z.b) or math.isnan(z.a) or math.isnan(z.b):
return i+1
else:
z = z_next
i+=1
return i
# Maps a number n in the range <min1, max1> to the range <min2, max2> such that the ratio of its distance from both ends of the interval remains the same
def map(n, min1, max1, min2, max2):
if math.isinf(n): # just in case
return max2
return (n - min1) / (max1 - min1) * (max2 - min2) + min2
# Function controlling one of the processes generating the Mandelbrot set image
'''
res[resolution] - both the length and width of the image in pixels
Larger res provide greater detail in the image
ite[iterations] - after how many iterations the program decides that applying the simple equation more times won't make the magnitude of the result > 2 (point belongs to Mandelbrot set)
Higher ite values give more detailed shape
total - total number of processes
id - process identifier (from 0 to total-1)
Through id and total, each process knows which part of the image to generate
queue - queue to which the program will submit fragments
'''
def mandelbrot_process(res, ite, total, id, queue, path=None):
heigth = res//total
prebake_div = 4/total
array = np.zeros((heigth, res), dtype=np.uint8)
try:
progress = 0
# for each point in the image, check after how many iterations of the simple equation (if any) it has moved more than 2 units away from (0,0) and color it accordingly
for y in range(heigth):
for x in range(res):
# convert pixel to complex number at the corresponding position
z = complex(
map(x, 0, res, -2, 2),
map(y,
0, heigth,
prebake_div * id - 2, prebake_div * (id+1) - 2)
)
# applying the simple equation is handled by this function (see def simle_equation(c, ite=2):)
score = simple_equation(z, ite)
# color the pixel accordingly
array[y][x] = map(score, 0, ite, 0, 255)
# update from processes
if 100*y/heigth > progress:
progress += 1
print(f"Finished {progress}% of process {id}! " + ["Awesome!", "Great!", "Amazing!", f"Good job process number {id}!"][random.randint(0,3)]) # motivating processes (100% required)
print(f"!\nFINISHED PROCESS {id} LET'S GOOOOO!\n!")
if path != None:
cv2.imwrite(path+f"/output_res{res}-ite{ite}-id{id}.jpg", array)
queue.put((id, array))
except Exception as e:
print(f"!\nWHAT JUST HAPPENED LIKE I DON'T EVEN KNOW??\nProcess {id} explain yourself!\n\nProcess {id}:")
print(e)
print("\n!")
# input but if you don't enter anything, it will be default
def input_int_with_def(default=0, prompt=""):
var = input(prompt + f"(default: {default})> ")
if var == "":
return default
else:
return int(var)
if __name__ == "__main__":
try:
# inputs
res = int(input("resolution (px)> "))
ite = input_int_with_def(12, "iterations")
pro = input_int_with_def(4, "processes")
# create a queue to collect results from individual processes
queue = multiprocessing.Queue()
# start all processes
processes = []
for id in range(pro):
p = multiprocessing.Process(target=mandelbrot_process, args=(res, ite, pro, id, queue, path))
processes.append(p)
p.start()
# wait until they finish
for p in processes:
p.join()
# create an empty array to store results from individual processes
results = [None] * pro
# retrieve results from the queue and place them in the appropriate positions in the results array
while not queue.empty():
id, result = queue.get()
results[id] = result
# concatenate results from individual processes into one image
final_image = np.vstack(results)
# save and display the image
please = cv2.imwrite(path+f"/output_res{res}-ite{ite}.jpg", final_image)
if please:
message = "Finished generating and saved correctly :)"
else:
message = "Finished generating !BUT SAVED INCORRECTLY! :("
print(message)
cv2.imshow("Mandelbrot_Set", final_image)
except Exception as e:
print("something happened in main")
print(e)
finally:
cv2.waitKey(1)
我不知道是什么导致了这种情况,但我相信你们这些好心的陌生人 :)
我尝试通过在代码周围放置打印语句来调试(这些没有包含在代码中),看起来所有进程都完成了它们的工作,但代码的下一部分(比如等待它们完成)没有被激活。
我还尝试用不同的分辨率和默认设置生成图像,程序在分辨率值为87或以下时正常工作,而88或以上就会出现上面描述的问题。我也不知道为什么 :S
1 个回答
1
你应该在这里使用 multiprocessing.Pool
(可以参考这个链接了解更多:https://docs.python.org/3/library/multiprocessing.html#multiprocessing.pool.Pool.map)。我对你的代码做了些小改动,就成功运行了。顺便提一下,complex
这个功能在Python里已经有了,不用自己再去实现。另外,给函数或变量命名时,尽量避免使用内置的名称,比如 map
。
以下是代码:
import math
import multiprocessing
from functools import partial
import cv2
import numpy as np
path = "./img"
def simple_equation(c, ite=2):
"""
Mandelbrot set is created by recursively applying the simple equation
Z_(n+1) = Z_n^2 - c where c is the initial number and Z_0=0 until the
magnitude of the number becomes > 2
"""
z = complex(0)
i = 0
while i <= ite and abs(z) < 2:
z = z * z - c
i += 1
return i
def map_range(n, min1, max1, min2, max2):
"""
Maps a number n in the range <min1, max1> to the range <min2, max2> such
that the ratio of its distance from both ends of the interval remains the
same
"""
if math.isinf(n): # just in case
return max2
return (n - min1) / (max1 - min1) * (max2 - min2) + min2
def mandelbrot_process(id, res, ite, total, path=None):
"""
Function controlling one of the processes generating the Mandelbrot set
image
"""
heigth = res // total
prebake_div = 4 / total
array = np.zeros((heigth, res), dtype=np.uint8)
try:
progress = 0
# for each point in the image, check after how many iterations of the
# simple equation (if any) it has moved more than 2 units away from
# (0,0) and color it accordingly
for y in range(heigth):
for x in range(res):
# convert pixel to complex number at the corresponding position
z = complex(
map_range(x, 0, res, -2, 2),
map_range(
y,
0,
heigth,
prebake_div * id - 2,
prebake_div * (id + 1) - 2,
),
)
# applying the simple equation is handled by this function (see
# def simle_equation(c, ite=2):)
score = simple_equation(z, ite)
# color the pixel accordingly
array[y][x] = map_range(score, 0, ite, 0, 255)
# update from processes
if 100 * y / heigth > progress:
progress += 1
print(f"!\nFINISHED PROCESS {id} LET'S GOOOOO!\n!")
if path is not None:
cv2.imwrite(path + f"/output_res{res}-ite{ite}-id{id}.jpg", array)
return (id, array)
except Exception as e:
print(
f"!\nWHAT JUST HAPPENED LIKE I DON'T EVEN KNOW??\nProcess {id} "
f"explain yourself!\n\nProcess {id}:"
)
print(e)
print("\n!")
def input_int_with_def(default=0, prompt=""):
"""
input but if you don't enter anything, it will be default
"""
var = input(prompt + f"(default: {default})> ")
if var == "":
return default
else:
return int(var)
if __name__ == "__main__":
try:
# inputs
res = int(input("resolution (px)> "))
ite = input_int_with_def(12, "iterations")
pro = input_int_with_def(4, "processes")
with multiprocessing.Pool(pro) as pool:
ret = pool.map(
partial(mandelbrot_process, res=res, ite=ite, total=pro, path=path),
range(pro),
)
# create an empty array to store results from individual processes
results = [None] * pro
# retrieve results from the queue and place them in the appropriate
# positions in the results array
for id, result in ret:
results[id] = result
# concatenate results from individual processes into one image
final_image = np.vstack(results)
# save and display the image
please = cv2.imwrite(path + f"/output_res{res}-ite{ite}.jpg", final_image)
if please:
message = "Finished generating and saved correctly :)"
else:
message = "Finished generating !BUT SAVED INCORRECTLY! :("
print(message)
cv2.imshow("Mandelbrot_Set", final_image)
except Exception as e:
print("something happened in main")
print(e)
finally:
cv2.waitKey(1)