multiprocessing trong python - quy trình forkserver kế thừa điều gì từ quy trình mẹ?
Tôi đang cố gắng sử dụng forkserver
và tôi gặp phải NameError: name 'xxx' is not defined
trong các quy trình của công nhân.
Tôi đang sử dụng Python 3.6.4, nhưng tài liệu phải giống nhau, từ https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods nó nói rằng:
Quy trình máy chủ fork là một luồng đơn nên có thể an toàn khi sử dụng os.fork (). Không có tài nguyên không cần thiết nào được kế thừa.
Ngoài ra, nó nói:
Tốt hơn để kế thừa hơn là dưa chua / bỏ chọn
Khi sử dụng các phương pháp khởi động spawn hoặc forkserver, nhiều loại từ quá trình đa xử lý cần phải được chọn để các quy trình con có thể sử dụng chúng . Tuy nhiên, thường nên tránh gửi các đối tượng được chia sẻ đến các quy trình khác bằng cách sử dụng đường ống hoặc hàng đợi. Thay vào đó, bạn nên sắp xếp chương trình để một quy trình cần truy cập vào tài nguyên được chia sẻ được tạo ở nơi khác có thể kế thừa nó từ quy trình tổ tiên.
Vì vậy, rõ ràng một đối tượng chính mà quy trình công nhân của tôi cần làm việc đã không được quy trình máy chủ kế thừa và sau đó chuyển cho công nhân, tại sao điều đó lại xảy ra? Tôi tự hỏi chính xác thì điều gì được quy trình forkserver kế thừa từ quy trình mẹ?
Đây là mã của tôi trông như thế nào:
import multiprocessing
import (a bunch of other modules)
def worker_func(nameList):
global largeObject
for item in nameList:
# get some info from largeObject using item as index
# do some calculation
return [item, info]
if __name__ == '__main__':
result = []
largeObject # This is my large object, it's read-only and no modification will be made to it.
nameList # Here is a list variable that I will need to get info for each item in it from the largeObject
ctx_in_main = multiprocessing.get_context('forkserver')
print('Start parallel, using forking/spawning/?:', ctx_in_main.get_context())
cores = ctx_in_main.cpu_count()
with ctx_in_main.Pool(processes=4) as pool:
for x in pool.imap_unordered(worker_func, nameList):
result.append(x)
Cảm ơn bạn!
Tốt,
Trả lời
Học thuyết
Dưới đây là một đoạn trích từ blog Bojan Nikolic
Các phiên bản Python hiện đại (trên Linux) cung cấp ba cách để bắt đầu các quy trình riêng biệt:
Fork () -ing các quy trình cha và tiếp tục với hình ảnh các quy trình giống nhau trong cả cha và con. Phương pháp này nhanh, nhưng có khả năng không đáng tin cậy khi trạng thái gốc phức tạp
Tạo ra các quy trình con, tức là fork () - ing và sau đó thực thi để thay thế hình ảnh quy trình bằng một quy trình Python mới. Phương pháp này đáng tin cậy nhưng chậm, vì hình ảnh quy trình được tải lại sau khi tải.
Cơ chế forkserver , bao gồm một máy chủ Python riêng biệt có trạng thái tương đối đơn giản và là fork () - ed khi cần một quy trình mới. Phương thức này kết hợp tốc độ của Fork () - ing với độ tin cậy tốt (bởi vì cha mẹ được fork ở trạng thái đơn giản).
Forkserver
Phương pháp thứ ba, forkserver , được minh họa bên dưới. Lưu ý rằng trẻ em giữ lại một bản sao của trạng thái máy chủ. Trạng thái này được dự định là tương đối đơn giản, nhưng có thể điều chỉnh điều này thông qua API đa quy trình thông qua
set_forkserver_preload()
phương thức.
Thực hành
Vì vậy, nếu bạn muốn simething được thừa hưởng bởi các quá trình con từ cha mẹ, điều này phải được quy định trong forkserver nhà nước bằng các phương tiện set_forkserver_preload(modules_names)
, trong đó thiết lập danh sách các tên mô-đun để cố gắng tải trong quá trình forkserver. Tôi đưa ra một ví dụ dưới đây:
# inherited.py
large_obj = {"one": 1, "two": 2, "three": 3}
# main.py
import multiprocessing
import os
from time import sleep
from inherited import large_obj
def worker_func(key: str):
print(os.getpid(), id(large_obj))
sleep(1)
return large_obj[key]
if __name__ == '__main__':
result = []
ctx_in_main = multiprocessing.get_context('forkserver')
ctx_in_main.set_forkserver_preload(['inherited'])
cores = ctx_in_main.cpu_count()
with ctx_in_main.Pool(processes=cores) as pool:
for x in pool.imap(worker_func, ["one", "two", "three"]):
result.append(x)
for res in result:
print(res)
Đầu ra:
# The PIDs are different but the address is always the same
PID=18603, obj id=139913466185024
PID=18604, obj id=139913466185024
PID=18605, obj id=139913466185024
Và nếu chúng ta không sử dụng tính năng tải trước
...
ctx_in_main = multiprocessing.get_context('forkserver')
# ctx_in_main.set_forkserver_preload(['inherited'])
cores = ctx_in_main.cpu_count()
...
# The PIDs are different, the addresses are different too
# (but sometimes they can coincide)
PID=19046, obj id=140011789067776
PID=19047, obj id=140011789030976
PID=19048, obj id=140011789030912
Vì vậy, sau một cuộc thảo luận đầy cảm hứng với Alex, tôi nghĩ rằng tôi có đủ thông tin để giải quyết câu hỏi của mình: chính xác thì quy trình forkserver sẽ kế thừa điều gì từ quy trình mẹ?
Về cơ bản khi quá trình máy chủ bắt đầu, nó sẽ nhập mô-đun chính của bạn và mọi thứ trước đó if __name__ == '__main__'
sẽ được thực thi. Đó là lý do tại sao mã của tôi không hoạt động, bởi vì large_object
không có nơi nào được tìm thấy trong server
quy trình và trong tất cả các quy trình công nhân đó tách khỏi server
quy trình .
Giải pháp của Alex hoạt động vì large_object
bây giờ được nhập vào cả quá trình chính và máy chủ, vì vậy mọi nhân viên được phân nhánh từ máy chủ cũng sẽ nhận được large_object
. Nếu kết hợp với set_forkserver_preload(modules_names)
tất cả các công nhân thậm chí có thể nhận được như nhau large_object
từ những gì tôi đã thấy. Lý do sử dụng forkserver
được giải thích rõ ràng trong tài liệu Python và trong blog của Bojan:
Khi chương trình khởi động và chọn phương pháp khởi động máy chủ, quá trình máy chủ sẽ được bắt đầu. Từ đó trở đi, bất cứ khi nào cần một quy trình mới, quy trình mẹ sẽ kết nối với máy chủ và yêu cầu nó phân nhánh một quy trình mới. Quy trình máy chủ fork là một luồng đơn nên có thể an toàn khi sử dụng os.fork (). Không có tài nguyên không cần thiết nào được kế thừa .
Cơ chế forkserver, bao gồm một máy chủ Python riêng biệt với trạng thái tương đối đơn giản và là fork () - ed khi cần một quy trình mới. Phương thức này kết hợp tốc độ của Fork () - ing với độ tin cậy tốt (bởi vì cha mẹ được fork ở trạng thái đơn giản) .
Vì vậy, nó nhiều hơn về mặt an toàn của mối quan tâm ở đây.
Một lưu ý nhỏ là, nếu bạn sử dụng fork
làm phương pháp bắt đầu, bạn không cần nhập bất kỳ thứ gì vì tất cả quy trình con đều nhận được bản sao của bộ nhớ quy trình cha (hoặc tham chiếu nếu hệ thống sử dụng COW- copy-on-write
, vui lòng sửa cho tôi nếu tôi Sai lầm). Trong trường hợp này sử dụng global large_object
sẽ giúp bạn có được quyền truy cập vào large_object
trong worker_func
trực tiếp.
Đây forkserver
có thể không phải là cách tiếp cận phù hợp với tôi vì vấn đề tôi đang phải đối mặt là chi phí bộ nhớ. Tất cả các thao tác đưa tôi large_object
vào vị trí đầu tiên đều tốn bộ nhớ, vì vậy tôi không muốn có bất kỳ tài nguyên không cần thiết nào trong các quy trình công nhân của mình.
Nếu tôi đặt tất cả các tính toán đó trực tiếp vào inherited.py
như Alex đề xuất, nó sẽ được thực thi hai lần (một lần khi tôi nhập mô-đun vào chính và một lần khi máy chủ nhập nó; thậm chí có thể nhiều hơn khi các quy trình công nhân được sinh ra?), Điều này phù hợp nếu tôi chỉ muốn một quy trình an toàn đơn luồng mà người lao động có thể phân nhánh. Nhưng vì tôi đang cố gắng để người lao động không thừa hưởng các tài nguyên không cần thiết và chỉ nhận được large_object
, điều này sẽ không hoạt động. Và đưa những tính toán ở __main__
trong inherited.py
sẽ không làm việc, hoặc kể từ bây giờ không ai trong số các quá trình sẽ thực hiện chúng, bao gồm cả chính và máy chủ.
Vì vậy, như một kết luận, nếu mục tiêu ở đây là để người lao động thừa hưởng các tài nguyên tối thiểu, thì tốt hơn hết tôi nên chia mã của mình thành 2, làm calculation.py
trước, chọn large_object
, thoát trình thông dịch và bắt đầu một mã mới để tải tiếp theo large_object
. Sau đó, tôi chỉ có thể phát điên với một trong hai fork
hoặc forkserver
.