The Story That Explains Multiprocessing 🏗️Multiprocessing समझाने वाली कहानी 🏗️一个讲清多进程的故事 🏗️
Now hire five independent contractors, each with their own crew, their own truck of tools and their own workspace. They really work in parallel — one lays the foundation while another frames, another wires, another plumbs. They only need to talk now and then (shared blueprints, material deliveries).
That is multiprocessing in Python. Each process is its own contractor, with its own Python interpreter, its own GIL and its own memory. True parallelism. The cost is coordination: passing messages between processes takes more work than passing them between threads.
अब पाँच अलग contractors रखिए, हर एक की अपनी टीम, औज़ारों से भरा अपना truck और अपनी जगह। वे सच में parallel काम करते हैं — एक नींव डालता है, दूसरा ढाँचा बनाता है, तीसरा wiring, चौथा plumbing। उन्हें बस कभी-कभी बात करनी होती है (साझा नक़्शे, सामान की delivery)।
यही Python में multiprocessing है। हर process अपना contractor है, अपने Python interpreter, अपने GIL और अपनी memory के साथ। असली parallelism। क़ीमत है तालमेल: processes के बीच संदेश भेजना threads के बीच भेजने से ज़्यादा मेहनत का काम है।
现在雇 五个独立的承包商,每人都有自己的队伍、一卡车工具和自己的工作区。他们真正地并行工作 — 一个打地基,一个搭框架,一个布线,一个装水管。他们只需偶尔沟通(共享图纸、材料配送)。
这就是 Python 的 多进程。每个进程都是一个独立的承包商,有自己的 Python 解释器、自己的 GIL 和自己的内存。真正的并行。代价是协调:在进程之间传递消息比在线程之间传递更费事。
The GIL belongs to one interpreter, not to the whole machine. Start 8 processes and you get 8 interpreters, 8 GILs, and up to 8 cores running Python bytecode at the same time. This is Python's way around the GIL for CPU-bound work.
GIL एक interpreter का होता है, पूरी machine का नहीं। 8 processes शुरू कीजिए और आपको 8 interpreters, 8 GILs, और एक साथ Python bytecode चलाते 8 तक cores मिलते हैं। CPU-bound काम के लिए GIL से बचने का Python का यही रास्ता है।
GIL 属于单个解释器,而不是整台机器。启动 8 个进程,你就得到 8 个解释器、8 个 GIL,以及最多 8 个核心同时运行 Python 字节码。这是 Python 在 CPU 密集型工作中绕过 GIL 的方法。
Look for the interactive boxes: Start-Method Lab (Section 05), Pool Lab (07), Pickle Checker (08), IPC Cost Lab (10), Shared-State Lab (11) and Bug Doctor (17). All their numbers come from real runs on our 2-core test machine.
Interactive डिब्बे खोजें: Start-Method Lab (Section 05), Pool Lab (07), Pickle Checker (08), IPC Cost Lab (10), Shared-State Lab (11) और Bug Doctor (17)। इनके सारे numbers हमारी 2-core test machine पर असली runs से हैं।
请留意这些互动框:启动方式实验室(第 05 节)、Pool 实验室(07)、Pickle 检查器(08)、进程间通信开销实验室(10)、共享状态实验室(11)和 Bug 医生(17)。所有数字都来自我们双核测试机上的真实运行。
Threads vs Processes — The Key Difference ⚖️Threads बनाम Processes — मुख्य अंतर ⚖️线程 vs 进程 — 根本区别 ⚖️
Threads share one memory space and one GIL. Processes each have their own.
Threads एक memory space और एक GIL बाँटते हैं। हर process का अपना होता है।
线程共享一个内存空间和一个 GIL。每个进程都有自己的一份。
| Aspectपहलू方面 | threading | multiprocessing |
|---|---|---|
| MemoryMemory内存 | Sharedसाझा共享 | Separate per processहर process की अलग每个进程独立 |
| Parallel on many coresकई cores पर parallel多核并行 | No (GIL) — except during I/Oनहीं (GIL) — I/O के दौरान छोड़कर否(GIL)— I/O 期间除外 | Yes — true parallelहाँ — असली parallel是 — 真正并行 |
| Start-up costशुरू करने की क़ीमत启动开销 | ~0.1 ms~0.1 ms约 0.1 毫秒 | ~1–3 ms with fork, ~40 ms with spawn (our machine)fork से ~1–3 ms, spawn से ~40 ms (हमारी machine)fork 约 1–3 毫秒,spawn 约 40 毫秒(我们的机器) |
| Memory per unitहर unit की memory每个单元的内存 | Small (stack is reserved, not filled)कम (stack reserved, भरा नहीं)小(栈只是预留,并未用满) | A whole interpreter — often 10+ MBपूरा interpreter — अक्सर 10+ MB一个完整解释器 — 常超过 10 MB |
| Sharing dataData बाँटना共享数据 | Free — just pass the objectमुफ़्त — बस object दे दें免费 — 直接传对象 | Pickled and copied, or shared memoryPickle होकर copy, या shared memory序列化后复制,或用共享内存 |
| Typical bugsआम bugs典型 bug | Race conditionsRace conditions竞态条件 | Pickling errors, lost updates in shared statePickling errors, साझा state में खोए updatesPickle 错误、共享状态中丢失更新 |
| Best forकिसके लिए最适合 | I/O-boundI/O-boundI/O 密集 | CPU-boundCPU-boundCPU 密集 |
The original said "~100 ms" to start a process and "~8 MB per thread / ~30 MB per process". We measured process start-up on our machine (Start-Method Lab, Section 05): about 1.5 ms with fork and 41 ms with spawn. The 8 MB per thread is only a reserved stack address range on Linux, not memory actually used. A fresh Python process typically uses around 10 MB or more, depending on what it imports.
मूल में process शुरू करने के लिए "~100 ms" और "~8 MB प्रति thread / ~30 MB प्रति process" कहा गया था। हमने अपनी machine पर process start-up मापा (Start-Method Lab, Section 05): fork से लगभग 1.5 ms और spawn से 41 ms। प्रति thread 8 MB Linux पर सिर्फ़ reserved stack address range है, असल में इस्तेमाल हुई memory नहीं। एक नया Python process आम तौर पर लगभग 10 MB या ज़्यादा लेता है, यह इस पर निर्भर है कि वह क्या import करता है।
原文说启动一个进程需"约 100 毫秒",以及"每线程约 8 MB / 每进程约 30 MB"。我们在自己的机器上测量了进程启动时间(启动方式实验室,第 05 节):fork 约 1.5 毫秒,spawn 约 41 毫秒。每线程 8 MB 只是 Linux 上预留的栈地址范围,并不是实际占用的内存。一个新的 Python 进程通常占用约 10 MB 或更多,取决于它导入了什么。
Your First Process — The Basics 🚀आपका पहला Process — बुनियाद 🚀你的第一个进程 — 基础 🚀
multiprocessing.Process looks almost exactly like threading.Thread. If you know threads, you already know most of processes: target, args, start(), join().
multiprocessing.Process लगभग बिल्कुल threading.Thread जैसा दिखता है। अगर आप threads जानते हैं, तो processes का ज़्यादातर हिस्सा पहले से जानते हैं: target, args, start(), join()।
multiprocessing.Process 和 threading.Thread 几乎一模一样。如果你会用线程,你就已经掌握了进程的大部分:target、args、start()、join()。
from multiprocessing import Process
import os
import time
def worker(name, duration):
print(f"[{name}] pid={os.getpid()} starting")
time.sleep(duration)
print(f"[{name}] pid={os.getpid()} done")
if __name__ == "__main__": # REQUIRED on Windows and macOS
print(f"Main pid={os.getpid()}")
processes = []
for i in range(3):
p = Process(target=worker, args=(f"P{i}", 2))
p.start()
processes.append(p)
for p in processes:
p.join()
print("All processes complete.")
Each child has its own process id (pid). All three slept for 2 seconds at the same time, so the whole program took about 2 seconds, not 6.
हर child का अपना process id (pid) है। तीनों ने एक साथ 2 seconds sleep किया, इसलिए पूरा program 6 नहीं, लगभग 2 seconds में चला।
每个子进程都有自己的进程 ID(pid)。三个进程同时 sleep 了 2 秒,所以整个程序大约用了 2 秒,而不是 6 秒。
With the spawn and forkserver start methods, each child starts a fresh Python and re-imports your script to find your functions. Code at the top level of the script runs again in every child. Put all process-starting code under if __name__ == "__main__": so it runs only in the parent.
spawn और forkserver start methods में हर child एक नया Python शुरू करता है और आपके functions ढूँढने के लिए आपकी script फिर से import करता है। Script के top level का code हर child में फिर चलता है। Process शुरू करने वाला सारा code if __name__ == "__main__": के नीचे रखिए ताकि वह सिर्फ़ parent में चले।
使用 spawn 和 forkserver 启动方式时,每个子进程都会启动一个全新的 Python,并 重新导入你的脚本 来找到你的函数。脚本顶层的代码会在每个子进程中再运行一遍。把所有启动进程的代码都放在 if __name__ == "__main__": 之下,让它只在父进程中运行。
What really happens without the guardGuard के बिना असल में क्या होता है没有保护时实际会发生什么
import multiprocessing as mp, os
ctx = mp.get_context("spawn") # the start method Windows and macOS use
def worker():
print("child is working")
print("top of script, pid", os.getpid())
p = ctx.Process(target=worker) # NO if __name__ == "__main__": guard!
p.start()
p.join()
print("exit code of child:", p.exitcode)
The original said each child "would try to start more children — an infinite recursion". Python stops that: the real run above shows the top-level print running twice (parent and child), then the child raises RuntimeError: An attempt has been made to start a new process before the current process has finished its bootstrapping phase, exits with code 1, and the worker never runs. Also, the guard is not only for Windows and macOS: since Python 3.14 the default start method on Linux is forkserver, which re-imports the script too.
मूल में कहा गया कि हर child "और children शुरू करने की कोशिश करेगा — infinite recursion"। Python इसे रोकता है: ऊपर का असली run top-level print को दो बार चलते दिखाता है (parent और child), फिर child RuntimeError: An attempt has been made to start a new process before the current process has finished its bootstrapping phase raise करता है, code 1 से exit होता है, और worker कभी नहीं चलता। साथ ही guard सिर्फ़ Windows और macOS के लिए नहीं: Python 3.14 से Linux पर default start method forkserver है, जो script को भी फिर से import करता है।
原文说每个子进程"会尝试启动更多子进程 — 无限递归"。Python 会阻止这种情况:上面的真实运行显示,顶层的 print 运行了 两次(父进程和子进程),然后子进程抛出 RuntimeError: An attempt has been made to start a new process before the current process has finished its bootstrapping phase,以退出码 1 结束,工作函数根本没有运行。另外,这个保护不只是为 Windows 和 macOS 准备的:从 Python 3.14 起,Linux 上的默认启动方式是 forkserver,它同样会重新导入脚本。
Daemon Processes — Background Workers That End With the Parent 👻Daemon Processes — Parent के साथ ख़त्म होने वाले Background Workers 👻守护进程 — 随父进程结束的后台工作者 👻
Daemon processes work like that: they run alongside the parent, and when the parent exits they are terminated automatically.
Daemon processes ऐसे ही काम करते हैं: वे parent के साथ चलते हैं, और parent के exit होने पर अपने-आप बंद कर दिए जाते हैं।
守护进程就是这样工作的:它们与父进程一起运行,父进程退出时,它们会被 自动终止。
Non-daemon children keep the parent waiting. Daemon children are terminated when the parent exits.
Non-daemon children parent को इंतज़ार कराते हैं। Daemon children parent के exit पर बंद कर दिए जाते हैं।
非守护子进程会让父进程等待。守护子进程在父进程退出时被终止。
Daemon in actionDaemon काम करते हुए守护进程实战
from multiprocessing import Process
import time
def background_monitor():
while True:
print(" [daemon] still watching...")
time.sleep(1)
if __name__ == "__main__":
p = Process(target=background_monitor)
p.daemon = True # MUST be set before start()
p.start()
print("[main] doing work for 3 seconds...")
time.sleep(3)
print("[main] exiting - daemon will be killed automatically")
# No p.join() needed - daemon dies with parent
The same program WITHOUT the daemon flagवही program daemon flag के बिना同一个程序,不设守护标志
from multiprocessing import Process
import time
def background_monitor():
while True:
print(" [child] still watching...")
time.sleep(1)
if __name__ == "__main__":
p = Process(target=background_monitor)
# p.daemon = True <-- omitted!
p.start()
time.sleep(3)
print("[main] want to exit...")
# PROGRAM HANGS FOREVER - the child loops, the parent waits for it
1. Set p.daemon = True (or Process(..., daemon=True)) before p.start(). Setting it after raises an error.
2. A daemon process cannot have children of its own — see the real run below.
3. Daemons are terminated abruptly (with terminate()): no finally blocks, no exit handlers. Never use them for work that needs a clean shutdown — open files, database transactions, half-written output.
1. p.daemon = True (या Process(..., daemon=True)) p.start() से पहले set कीजिए। बाद में set करने पर error आता है।
2. Daemon process के अपने children नहीं हो सकते — नीचे असली run देखिए।
3. Daemons अचानक बंद होते हैं (terminate() से): न finally blocks, न exit handlers। साफ़ shutdown चाहने वाले काम के लिए इन्हें कभी मत लीजिए — खुली files, database transactions, आधा लिखा output।
1. 在 p.start() 之前 设置 p.daemon = True(或 Process(..., daemon=True))。之后再设置会报错。
2. 守护进程 不能有自己的子进程 — 见下面的真实运行。
3. 守护进程会被 突然终止(用 terminate()):不运行 finally 块,也不运行退出处理函数。需要干净关闭的工作绝不要用它 — 打开的文件、数据库事务、写了一半的输出。
from multiprocessing import Process
def grandchild():
print("grandchild running")
def daemon_worker():
p = Process(target=grandchild) # a daemon tries to start a child
p.start()
p.join()
if __name__ == "__main__":
d = Process(target=daemon_worker, daemon=True)
d.start()
d.join()
print("daemon exit code:", d.exitcode)
| Background logging / metrics collectorBackground logging / metrics collector后台日志 / 指标收集器 |
| Heartbeat sender to a health-check serviceHealth-check service को heartbeat भेजना向健康检查服务发送心跳 |
| Idle-connection watchdogIdle-connection watchdog空闲连接看门狗 |
| Periodic cache refresherसमय-समय पर cache refresh定期刷新缓存 |
| Any "if the parent dies, this is pointless" workकोई भी "parent मरे तो यह बेकार" वाला काम任何"父进程没了,这也就没意义"的工作 |
| Writing important data to disk or a databaseज़रूरी data disk या database में लिखना把重要数据写入磁盘或数据库 |
| Uploading files to cloud storageCloud storage पर files upload करना上传文件到云存储 |
| Rendering or encoding a videoVideo render या encode करना渲染或编码视频 |
| An ML training epoch that must finishML training का epoch जो पूरा होना ही चाहिए必须完成的一轮机器学习训练 |
| Anything with clean-up in a finally blockजिसमें finally block में सफ़ाई हो任何在 finally 块中有清理逻辑的工作 |
If you want background work and a clean shutdown, do not use daemon=True. Run a normal process and give it an Event: the parent calls event.set() when it exits, the child notices and leaves its loop. You get "stops with the parent" plus proper clean-up.
अगर आपको background काम और साफ़ shutdown दोनों चाहिए, तो daemon=True मत लीजिए। एक सामान्य process चलाइए और उसे एक Event दीजिए: parent exit होते समय event.set() बुलाता है, child इसे देखकर अपना loop छोड़ देता है। आपको "parent के साथ रुकना" और सही सफ़ाई दोनों मिलते हैं।
如果你既想要后台工作,又 想要干净的关闭,就不要用 daemon=True。运行一个普通进程并给它一个 Event:父进程退出时调用 event.set(),子进程察觉后跳出循环。这样既能"随父进程停止",又能正确清理。
from multiprocessing import Process, Event
import time
import atexit
def graceful_monitor(stop_event):
try:
while not stop_event.is_set():
print(" [worker] doing work...")
stop_event.wait(timeout=1) # wakes early if signalled
finally:
print(" [worker] shutting down cleanly - flushing state")
if __name__ == "__main__":
stop = Event()
p = Process(target=graceful_monitor, args=(stop,))
p.start()
atexit.register(lambda: (stop.set(), p.join(timeout=5)))
time.sleep(3)
print("[main] done")
Look at the last line: it printed after "[main] done". The atexit handler set the event when the parent finished, and the worker's finally block ran. A daemon would have been cut off instead.
आख़िरी line देखिए: यह "[main] done" के बाद print हुई। Parent के ख़त्म होने पर atexit handler ने event set किया, और worker का finally block चला। Daemon होता तो वह बीच में कट जाता।
看最后一行:它在"[main] done"之后 打印。父进程结束时 atexit 处理函数设置了事件,工作进程的 finally 块得以运行。如果是守护进程,它就会被直接切断。
Fork vs Spawn — How Processes Are Created 🌱Fork बनाम Spawn — Processes कैसे बनते हैं 🌱Fork vs Spawn — 进程是如何创建的 🌱
Fork copies the running parent. Spawn starts a new interpreter that re-imports your script.
Fork चलते parent की copy बनाता है। Spawn एक नया interpreter शुरू करता है जो आपकी script फिर से import करता है।
fork 复制正在运行的父进程。spawn 启动一个新的解释器并重新导入你的脚本。
import multiprocessing as mp
# Set the start method explicitly for the same behaviour on every platform
if __name__ == "__main__":
print("Default here:", mp.get_start_method(allow_none=True) or "(not chosen yet)")
mp.set_start_method("spawn") # or "fork", "forkserver" - call once only
print("Available: ", mp.get_all_start_methods())
print("Current: ", mp.get_start_method())
The original called spawn "the modern default". Spawn is the default on Windows and on macOS (since 3.8). On Linux the default was fork up to 3.13 and is forkserver since Python 3.14. Also note: set_start_method() may be called only once per program — a second call raises RuntimeError: context has already been set. To use a different method in one place, use mp.get_context("spawn").
मूल ने spawn को "आधुनिक default" कहा। Spawn Windows पर और macOS (3.8 से) पर default है। Linux पर default 3.13 तक fork था और Python 3.14 से forkserver है। ध्यान दें: set_start_method() हर program में सिर्फ़ एक बार बुलाया जा सकता है — दूसरी call RuntimeError: context has already been set raise करती है। किसी एक जगह अलग method चाहिए तो mp.get_context("spawn") लीजिए।
原文称 spawn 是"现代默认方式"。spawn 是 Windows 和 macOS(自 3.8 起) 上的默认方式。在 Linux 上,3.13 及以前默认是 fork,从 Python 3.14 起是 forkserver。另外注意:set_start_method() 每个程序只能调用一次 — 第二次调用会抛出 RuntimeError: context has already been set。如果只想在某处使用不同的方式,请用 mp.get_context("spawn")。
🌱 Simulator: Start-Method Lab🌱 Simulator: Start-Method Lab🌱 模拟器:启动方式实验室
The same script was run with each start method. The parent changes a global CONFIG before starting a child. Does the child see the change? Is the module loaded again? How long does one start + join take? All real.
वही script हर start method से चलाई गई। Parent child शुरू करने से पहले एक global CONFIG बदलता है। क्या child को बदलाव दिखता है? क्या module फिर load होता है? एक start + join में कितना समय लगता है? सब असली।
同一个脚本分别用三种启动方式运行。父进程在启动子进程之前修改了全局变量 CONFIG。子进程能看到修改吗?模块会被再次加载吗?一次启动 + join 要多久?全部是真实数据。
Use spawn (or forkserver) for portability and safety: the child starts clean, and your code will behave the same on every operating system. Use fork only on Linux when start-up speed really matters and your program has no other threads (forking a process that has threads can deadlock). Use forkserver when you start many workers from a clean state on Linux.
Portability और सुरक्षा के लिए spawn (या forkserver) लीजिए: child साफ़ शुरू होता है, और आपका code हर operating system पर एक जैसा चलेगा। fork सिर्फ़ Linux पर तब लीजिए जब start-up गति सच में मायने रखे और आपके program में कोई दूसरे threads न हों (threads वाले process को fork करने से deadlock हो सकता है)। Linux पर साफ़ state से कई workers शुरू करने हों तो forkserver लीजिए।
为了可移植性和安全,使用 spawn(或 forkserver):子进程从干净状态开始,代码在所有操作系统上表现一致。只有在 Linux 上真正在意启动速度、且程序没有其他线程时才用 fork(fork 一个带线程的进程可能导致死锁)。在 Linux 上需要从干净状态启动大量工作进程时,用 forkserver。
Pool — The Workhorse of Parallel Python 🐎Pool — Parallel Python का मेहनती घोड़ा 🐎Pool — 并行 Python 的主力 🐎
Creating and joining processes by hand gets tedious. multiprocessing.Pool starts a fixed group of worker processes once and gives you high-level methods to spread work across them.
हाथ से processes बनाना और join करना थकाऊ हो जाता है। multiprocessing.Pool worker processes का एक तय समूह एक बार शुरू करता है और उनमें काम बाँटने के ऊँचे-स्तर के methods देता है।
手动创建和等待进程很快就会变得繁琐。multiprocessing.Pool 一次性启动一组固定的工作进程,并提供高级方法把工作分配给它们。
Pool starts N workers once and feeds them tasks through an internal queue.
Pool N workers एक बार शुरू करता है और अंदर की queue से उन्हें tasks देता है।
Pool 一次性启动 N 个工作进程,并通过内部队列给它们分配任务。
from multiprocessing import Pool
import time
def heavy_compute(n):
# Simulated CPU work
total = 0
for i in range(10_000_000):
total += i * n
return n, total
if __name__ == "__main__":
numbers = list(range(1, 9))
# --- Sequential ---
start = time.perf_counter()
results = [heavy_compute(n) for n in numbers]
print(f"Sequential: {time.perf_counter() - start:.2f}s")
# --- Pool with 4 workers ---
start = time.perf_counter()
with Pool(processes=4) as pool:
results = pool.map(heavy_compute, numbers)
print(f"Pool(4): {time.perf_counter() - start:.2f}s")
for n, total in results:
print(f" {n}: {total}")
The original showed 6.42 s → 1.71 s ("~3.75× speedup") on an 8-core machine. On our 2-core machine the real result is 3.73 s → 1.87 s: about 2×. Asking for 4 workers on 2 cores does not give 4× — at most 2 processes can run at once. Expect a speed-up close to the number of cores you really have (minus some start-up and pickling cost), never more.
मूल ने 8-core machine पर 6.42 s → 1.71 s ("~3.75× speedup") दिखाया। हमारी 2-core machine पर असली नतीजा 3.73 s → 1.87 s है: लगभग 2×। 2 cores पर 4 workers माँगने से 4× नहीं मिलता — एक साथ ज़्यादा से ज़्यादा 2 processes चल सकते हैं। Speed-up आपके असली cores की संख्या के क़रीब (start-up और pickling की कुछ क़ीमत घटाकर) की उम्मीद रखिए, उससे ज़्यादा कभी नहीं।
原文在 8 核机器上展示了 6.42 秒 → 1.71 秒("约 3.75 倍加速")。在我们的双核机器上,真实结果是 3.73 秒 → 1.87 秒:约 2 倍。在 2 个核心上要求 4 个工作进程并不会得到 4 倍 — 同一时刻最多只有 2 个进程在运行。加速比只会接近你 实际拥有的核心数(再减去一些启动和序列化开销),绝不会更高。
Pool Methods — Choosing the Right One 🧰Pool Methods — सही चुनना 🧰Pool 方法 — 选对那一个 🧰
| MethodMethod方法 | Behaviourव्यवहार行为 | Returnsलौटाता है返回 | Use whenकब लें适用场景 |
|---|---|---|---|
map(fn, iter) | Blocks, orderedरुकता है, क्रम में阻塞,有序 | List of results, in input orderInput क्रम में results की list按输入顺序的结果列表 | You need all results togetherसारे results एक साथ चाहिए需要一次拿到全部结果 |
map_async(fn, iter) | Does not block, orderedनहीं रुकता, क्रम में不阻塞,有序 | AsyncResult (call .get())AsyncResult (.get() बुलाएँ)AsyncResult(调用 .get()) | You want to do other work meanwhileबीच में दूसरा काम करना है想同时做别的事 |
imap(fn, iter) | Lazy iterator, orderedLazy iterator, क्रम में惰性迭代器,有序 | Iterator (streams results)Iterator (results बहते आते हैं)迭代器(流式返回结果) | Big input; use results as they comeबड़ा input; results आते ही इस्तेमाल输入很大;结果一到就处理 |
imap_unordered(fn, iter) | Lazy iterator, NOT orderedLazy iterator, क्रम में नहीं惰性迭代器,无序 | Iterator (fastest first)Iterator (सबसे तेज़ पहले)迭代器(先完成先返回) | Order does not matter; best throughputक्रम ज़रूरी नहीं; सबसे अच्छा throughput不在乎顺序;吞吐量最高 |
apply(fn, args) | Blocks the caller, one callCaller को रोकता है, एक call阻塞调用方,单次调用 | One resultएक result一个结果 | Rarely useful — you wait while one worker runs itकम ही उपयोगी — एक worker के चलाने तक आप रुकते हैं很少用 — 一个工作进程执行时你只能等 |
apply_async(fn, args) | Does not block, one callनहीं रुकता, एक call不阻塞,单次调用 | AsyncResult | Send off single tasksअकेले tasks भेजना单独提交任务 |
starmap(fn, iter_of_tuples) | Blocks, ordered, unpacks tuplesरुकता है, क्रम में, tuples खोलता है阻塞,有序,解包元组 | List of resultsResults की list结果列表 | The function takes several argumentsFunction कई arguments लेता है函数接受多个参数 |
Practical examples of eachहर एक के व्यावहारिक उदाहरण每种方法的实际示例
from multiprocessing import Pool
def square(x): return x * x
def add(a, b): return a + b
def on_complete(results):
print(f"Done! {len(results)} results")
if __name__ == "__main__":
with Pool(4) as pool:
# 1. map - simplest, blocks until done, results in order
print(pool.map(square, [1, 2, 3, 4, 5]))
# 2. starmap - for multi-argument functions
print(pool.starmap(add, [(1, 2), (3, 4), (5, 6)]))
# 3. imap_unordered - stream results as they finish
for result in pool.imap_unordered(square, range(10)):
print(result, end=" ")
print() # finish the line
# 4. apply_async - fire off a single task without blocking
r = pool.apply_async(square, (10,))
# ... do other work ...
print(r.get(timeout=5))
# 5. map_async with a callback
r = pool.map_async(square, range(100), callback=on_complete)
r.wait()
In the original, print(result, end=" ") in the imap_unordered loop had no closing print(), so the next result (100) would be glued onto the same line. We added print(). We also moved on_complete to module level, next to the other functions, which keeps the example tidy. And the table said apply "blocks the pool": it blocks the caller — the other workers stay free.
मूल में imap_unordered loop के print(result, end=" ") के बाद कोई print() नहीं था, इसलिए अगला result (100) उसी line पर चिपक जाता। हमने print() जोड़ा। on_complete को बाक़ी functions के पास module level पर रखा, जिससे example साफ़ रहता है। और table में कहा गया था कि apply "pool को रोकता है": यह caller को रोकता है — बाक़ी workers ख़ाली रहते हैं।
原文 imap_unordered 循环中的 print(result, end=" ") 后面没有收尾的 print(),所以下一个结果(100)会粘在同一行。我们加上了 print()。我们还把 on_complete 移到了模块级,和其他函数放在一起,让示例更整洁。另外,表格说 apply"阻塞线程池":其实它阻塞的是 调用方 — 其他工作进程仍然空闲。
🐎 Simulator: Pool Lab (real traces)🐎 Simulator: Pool Lab (असली traces)🐎 模拟器:Pool 实验室(真实追踪)
8 CPU tasks of different sizes (task #0 is the biggest). Choose the number of workers, the method and the chunksize. The bars show which worker process ran which task; the row underneath shows when each result reached your code. 12 real runs.
अलग-अलग size के 8 CPU tasks (task #0 सबसे बड़ा)। Workers की संख्या, method और chunksize चुनिए। Bars दिखाती हैं कि कौन-सा worker process कौन-सा task चला; नीचे की row दिखाती है कि हर result आपके code तक कब पहुँचा। 12 असली runs।
8 个大小不同的 CPU 任务(任务 #0 最大)。选择工作进程数、方法和 chunksize。条形图显示哪个工作进程执行了哪个任务;下面一行显示 每个结果何时到达你的代码。12 次真实运行。
For long jobs (image processing, ML inference) use imap_unordered: results stream in the moment they finish, so you can show progress. For many tiny tasks, pass a chunksize (for example 10 or 100) so each worker gets a batch instead of one item per round trip. map() already picks a chunksize for you; imap() and imap_unordered() use 1 unless you say otherwise. In the Pool Lab, notice that chunksize=4 with 2 workers sends tasks #0–3 to one worker, so the big task #0 delays three small ones.
लंबे jobs (image processing, ML inference) के लिए imap_unordered लीजिए: results ख़त्म होते ही आते जाते हैं, इसलिए आप प्रगति दिखा सकते हैं। बहुत छोटे-छोटे कई tasks के लिए chunksize दीजिए (जैसे 10 या 100) ताकि हर worker को एक-एक item की जगह एक batch मिले। map() आपके लिए ख़ुद chunksize चुनता है; imap() और imap_unordered() 1 लेते हैं जब तक आप न बताएँ। Pool Lab में देखिए कि 2 workers के साथ chunksize=4 tasks #0–3 एक worker को देता है, इसलिए बड़ा task #0 तीन छोटे tasks को देर कराता है।
对于长时间任务(图像处理、机器学习推理),用 imap_unordered:结果一完成就流式返回,所以你可以显示进度。对于 大量很小的任务,传入 chunksize(例如 10 或 100),让每个工作进程一次拿一批,而不是每次往返只拿一个。map() 会自动为你选择 chunksize;imap() 和 imap_unordered() 默认是 1。在 Pool 实验室中注意:2 个工作进程、chunksize=4 时,任务 #0–3 都交给同一个工作进程,于是大任务 #0 拖慢了三个小任务。
multiprocessing.Queue — Inter-Process Communication 📫multiprocessing.Queue — Processes के बीच बातचीत 📫multiprocessing.Queue — 进程间通信 📫
multiprocessing.Queue is the pneumatic tube of Python processes. Objects are pickled (turned into bytes), sent through an OS pipe, then unpickled on the other side. Slower than passing a reference between threads, but a safe way to move data between processes.multiprocessing.Queue Python processes की pneumatic tube है। Objects pickle होते हैं (bytes में बदलते हैं), OS pipe से भेजे जाते हैं, फिर दूसरी ओर unpickle होते हैं। Threads के बीच reference देने से धीमा, पर processes के बीच data भेजने का सुरक्षित तरीक़ा।multiprocessing.Queue 就是 Python 进程之间的气动管道。对象被 pickle(转成字节),通过操作系统管道发送,然后在另一端反序列化。比在线程之间传递引用慢,但这是在进程之间传递数据的安全方式。multiprocessing.Queue pickles the object, sends the bytes through a pipe, and unpickles a copy on the other side.
multiprocessing.Queue object को pickle करता है, bytes pipe से भेजता है, और दूसरी ओर एक copy unpickle करता है।
multiprocessing.Queue 把对象 pickle,通过管道发送字节,再在另一端反序列化出一个副本。
Producer-consumer between processesProcesses के बीच producer-consumer进程之间的生产者-消费者
from multiprocessing import Process, Queue
import time
import os
def producer(q, count):
for i in range(count):
item = {"id": i, "pid": os.getpid(), "payload": i * 2}
q.put(item)
print(f"[P] produced {item}")
time.sleep(0.1)
q.put(None) # poison pill
def consumer(q):
while True:
item = q.get()
if item is None: break
print(f" [C pid={os.getpid()}] got {item}")
time.sleep(0.15)
if __name__ == "__main__":
q = Queue(maxsize=10)
p1 = Process(target=producer, args=(q, 5))
p2 = Process(target=consumer, args=(q,))
p1.start(); p2.start()
p1.join(); p2.join()
print("Done.")
The original said queue items travel "across an OS pipe or shared memory". multiprocessing.Queue uses a pipe: a hidden background "feeder" thread in the sending process pickles each item and writes it into the pipe. That feeder thread is also why put() returns before the data has really left — and why joining a process that still has unsent data can hang (Bug Doctor, Section 17).
मूल में कहा गया कि queue items "OS pipe या shared memory से" जाते हैं। multiprocessing.Queue एक pipe इस्तेमाल करता है: भेजने वाले process में एक छिपा background "feeder" thread हर item को pickle करके pipe में लिखता है। इसी feeder thread की वजह से put() data के सच में निकलने से पहले लौट आता है — और इसी वजह से ऐसे process को join करना जिसका data अभी नहीं गया, अटक सकता है (Bug Doctor, Section 17)।
原文说队列元素"通过操作系统管道或共享内存"传输。multiprocessing.Queue 使用的是管道:发送进程中有一个隐藏的后台"馈送"线程,负责把每个元素 pickle 后写入管道。正因为有这个馈送线程,put() 会在数据真正发出之前就返回 — 这也是为什么等待一个还有未发送数据的进程可能会卡住(Bug 医生,第 17 节)。
Lambdas, functions defined inside other functions, open files, locks, generators, database connections and sockets cannot be pickled, so they cannot cross a process boundary. Stick to plain data (dicts, lists, numbers, strings), NumPy arrays and instances of module-level classes. Use module-level def functions, not lambdas. Real run:
Lambdas, दूसरे functions के अंदर बने functions, खुली files, locks, generators, database connections और sockets pickle नहीं हो सकते, इसलिए process की सीमा पार नहीं कर सकते। सादा data (dicts, lists, numbers, strings), NumPy arrays और module-level classes के instances लीजिए। Lambdas नहीं, module-level def functions लीजिए। असली run:
lambda、定义在其他函数内部的函数、打开的文件、锁、生成器、数据库连接和套接字都不能被 pickle,所以无法跨越进程边界。请使用普通数据(字典、列表、数字、字符串)、NumPy 数组和模块级类的实例。使用模块级的 def 函数,而不是 lambda。真实运行:
from multiprocessing import Pool
if __name__ == "__main__":
with Pool(2) as pool:
print(pool.map(lambda x: x * 2, [1, 2, 3])) # a lambda cannot be pickled
🥒 Simulator: Pickle Checker🥒 Simulator: Pickle Checker🥒 模拟器:Pickle 检查器
Can this object be sent to another process? Each answer is the real result of pickle.dumps() on Python 3.13.16.
क्या यह object दूसरे process को भेजा जा सकता है? हर जवाब Python 3.13.16 पर pickle.dumps() का असली नतीजा है।
这个对象能发送给另一个进程吗?每个答案都是在 Python 3.13.16 上运行 pickle.dumps() 的真实结果。
Pipes — The Two-Endpoint Alternative 🔌Pipes — दो सिरों वाला विकल्प 🔌管道 — 两端点的替代方案 🔌
multiprocessing.Pipe() returns two connected ends for two-way communication between exactly two processes. It is faster than a Queue for point-to-point traffic, but it is not safe for many producers or many consumers on the same end.
multiprocessing.Pipe() ठीक दो processes के बीच दो-तरफ़ा बातचीत के लिए दो जुड़े सिरे लौटाता है। सीधी बातचीत के लिए यह Queue से तेज़ है, पर एक ही सिरे पर कई producers या कई consumers के लिए सुरक्षित नहीं।
multiprocessing.Pipe() 返回两个相连的端点,用于 恰好两个 进程之间的双向通信。对于点对点通信,它比 Queue 快,但同一端有多个生产者或消费者时并不安全。
from multiprocessing import Process, Pipe
def worker(conn):
conn.send({"status": "ready", "pid": 42})
reply = conn.recv() # wait for the parent's message
print(f"Worker got reply: {reply}")
conn.close()
if __name__ == "__main__":
parent_conn, child_conn = Pipe()
p = Process(target=worker, args=(child_conn,))
p.start()
print("Parent got:", parent_conn.recv())
parent_conn.send("go ahead")
p.join()
| Propertyगुण特性 | Queue | Pipe |
|---|---|---|
| Endsसिरे端点 | Many producers, many consumersकई producers, कई consumers多个生产者,多个消费者 | Exactly 2 endsठीक 2 सिरे恰好 2 个端点 |
| Speed (our 100 MB test)गति (हमारा 100 MB test)速度(我们的 100 MB 测试) | Slower — extra feeder thread and locksधीमा — अतिरिक्त feeder thread और locks较慢 — 多了馈送线程和锁 | Faster — a raw OS pipeतेज़ — सीधा OS pipe较快 — 原始操作系统管道 |
| Safe with several users on one endएक सिरे पर कई users के साथ सुरक्षित同一端多人使用是否安全 | Yesहाँ是 | No — add your own lockनहीं — अपना lock जोड़ें否 — 需自己加锁 |
| API | put() / get() | send() / recv() |
| Best forकिसके लिए最适合 | Work-queue patternsWork-queue patterns工作队列模式 | Parent-child two-way chatParent-child दो-तरफ़ा बातचीत父子进程双向通信 |
Shared Memory — When Pickling Is Too Slow 💾Shared Memory — जब Pickling बहुत धीमा हो 💾共享内存 — 当 pickle 太慢时 💾
Pickling a big NumPy array to send it through a Queue is slow. For large data, Python offers real shared memory: one block of RAM that several processes read and write directly, with no copying.
बड़े NumPy array को Queue से भेजने के लिए pickle करना धीमा है। बड़े data के लिए Python असली shared memory देता है: RAM का एक block जिसे कई processes सीधे पढ़ते और लिखते हैं, बिना copy किए।
为了通过 Queue 发送一个大的 NumPy 数组而对它进行 pickle 是很慢的。对于大数据,Python 提供了 真正的共享内存:一块多个进程可以直接读写的内存,无需复制。
Value/Array for small numbers, Manager for any Python object, shared_memory for big numeric data.
छोटे numbers के लिए Value/Array, किसी भी Python object के लिए Manager, बड़े numeric data के लिए shared_memory।
小数值用 Value/Array,任意 Python 对象用 Manager,大块数值数据用 shared_memory。
📦 Simulator: IPC Cost Lab📦 Simulator: IPC Cost Lab📦 模拟器:进程间通信开销实验室
How long does it take to hand a NumPy array to another process and get a tiny answer back? Measured on our machine (best of 2 runs). For shared_memory only the block's name travels; the child attaches and reads the data in place.
एक NumPy array दूसरे process को देकर छोटा-सा जवाब वापस पाने में कितना समय लगता है? हमारी machine पर मापा गया (2 runs में सबसे अच्छा)। shared_memory में सिर्फ़ block का नाम जाता है; child जुड़कर data वहीं पढ़ता है।
把一个 NumPy 数组交给另一个进程并拿回一个很小的答案需要多久?在我们的机器上测得(2 次中取最好)。使用 shared_memory 时只传递内存块的 名字;子进程连接后直接在原处读取数据。
Shared Memory — Value and Array (Simple C Types) 🔢Shared Memory — Value और Array (सरल C Types) 🔢共享内存 — Value 与 Array(简单 C 类型)🔢
For a shared counter or a small fixed-size array of numbers, use Value and Array. They hold C-level numbers in shared memory and come with an optional lock.
साझा counter या numbers की छोटी, तय-size array के लिए Value और Array लीजिए। ये shared memory में C-level numbers रखते हैं और एक वैकल्पिक lock के साथ आते हैं।
对于共享计数器或固定大小的小型数值数组,使用 Value 和 Array。它们在共享内存中保存 C 级别的数值,并自带一个可选的锁。
from multiprocessing import Process, Value, Array, Lock
def increment(counter, lock, n):
for _ in range(n):
with lock: # atomic increment
counter.value += 1
def square_in_place(arr, start, end):
for i in range(start, end):
arr[i] = arr[i] * arr[i]
if __name__ == "__main__":
# --- Shared counter ---
counter = Value('i', 0) # 'i' = signed int
lock = Lock()
procs = [Process(target=increment, args=(counter, lock, 10_000))
for _ in range(4)]
for p in procs: p.start()
for p in procs: p.join()
print(f"Final counter: {counter.value}")
# --- Shared array ---
arr = Array('d', range(20)) # 'd' = double
p1 = Process(target=square_in_place, args=(arr, 0, 10))
p2 = Process(target=square_in_place, args=(arr, 10, 20))
p1.start(); p2.start()
p1.join(); p2.join()
print(list(arr[:]))
'i' = int, 'd' = double, 'f' = float, 'b' = signed char, 'B' = unsigned char, 'c' = char — the same codes as Python's array module. A Value already has its own lock: with counter.get_lock(): works without creating a separate Lock(). But counter.value += 1 on its own is still read-then-write, so it still needs that lock — see the Shared-State Lab below.
'i' = int, 'd' = double, 'f' = float, 'b' = signed char, 'B' = unsigned char, 'c' = char — Python के array module वाले ही codes। एक Value के पास पहले से अपना lock है: अलग Lock() बनाए बिना with counter.get_lock(): चलता है। पर अकेला counter.value += 1 अब भी पढ़ो-फिर-लिखो है, इसलिए उसे वह lock चाहिए — नीचे Shared-State Lab देखिए।
'i' = int,'d' = double,'f' = float,'b' = 有符号 char,'B' = 无符号 char,'c' = char — 与 Python 的 array 模块相同。Value 本身已经带有一把锁:不用另建 Lock(),直接写 with counter.get_lock(): 即可。但单独的 counter.value += 1 仍然是"先读再写",所以仍需要那把锁 — 见下面的共享状态实验室。
🔀 Simulator: Shared-State Lab🔀 Simulator: Shared-State Lab🔀 模拟器:共享状态实验室
4 processes each add 1 to a counter 5,000 times (expected total: 20,000). Four ways to hold the counter, each run 3 times for real. Compare the totals and the times.
4 processes हर एक counter में 5,000 बार 1 जोड़ते हैं (अपेक्षित कुल: 20,000)। Counter रखने के चार तरीक़े, हर एक 3 बार असल में चलाया गया। कुल और समय की तुलना कीजिए।
4 个进程各自给计数器加 1,共 5,000 次(预期总数:20,000)。计数器有四种保存方式,每种都真实运行 3 次。比较总数和用时。
Shared Memory — Manager (Any Python Object) 🗂️Shared Memory — Manager (कोई भी Python Object) 🗂️共享内存 — Manager(任意 Python 对象)🗂️
To share dicts, lists or other Python objects, use a Manager. It starts a separate server process that owns the real object; every access from a worker is a small request to that server (a remote procedure call).
Dicts, lists या दूसरे Python objects बाँटने के लिए Manager लीजिए। यह एक अलग server process शुरू करता है जो असली object रखता है; worker की हर पहुँच उस server को एक छोटी request है (remote procedure call)।
要共享字典、列表或其他 Python 对象,使用 Manager。它会启动一个单独的 服务器进程 来持有真正的对象;工作进程的每次访问都是向该服务器发出的一个小请求(远程过程调用)。
from multiprocessing import Process, Manager
def worker(shared_dict, shared_list, worker_id):
shared_dict[worker_id] = f"done by worker {worker_id}"
shared_list.append(worker_id ** 2)
if __name__ == "__main__":
with Manager() as manager:
shared_dict = manager.dict()
shared_list = manager.list()
procs = [Process(target=worker, args=(shared_dict, shared_list, i))
for i in range(5)]
for p in procs: p.start()
for p in procs: p.join()
print("Dict:", dict(shared_dict))
print("List:", list(shared_list))
The original showed the dict keys and the list in perfect order [0, 1, 4, 9, 16]. In our real run workers 3 and 4 finished first, so the dict starts with keys 3 and 4 and the list is [9, 16, 0, 1, 4]. The processes run in parallel, so they write in whatever order they finish. If you need order, sort afterwards or store results by key.
मूल ने dict keys और list को बिल्कुल क्रम में [0, 1, 4, 9, 16] दिखाया। हमारे असली run में workers 3 और 4 पहले ख़त्म हुए, इसलिए dict keys 3 और 4 से शुरू होती है और list [9, 16, 0, 1, 4] है। Processes parallel चलते हैं, इसलिए जिस क्रम में ख़त्म हों उसी क्रम में लिखते हैं। क्रम चाहिए तो बाद में sort कीजिए या results को key से रखिए।
原文显示字典的键和列表都是完美有序的 [0, 1, 4, 9, 16]。在我们的真实运行中,工作进程 3 和 4 最先完成,所以字典以键 3 和 4 开头,列表是 [9, 16, 0, 1, 4]。进程是并行运行的,按完成的先后写入。如果需要顺序,事后排序或按键存储结果。
Every read or write to a Manager object is a round trip to the server process. In the Shared-State Lab, 20,000 locked increments took about 6.5 s with a Manager dict, but about 0.05 s with a Value — over 100× slower. Use a Manager for convenience at moderate access rates, not in hot loops.
Manager object को हर read या write server process तक आना-जाना है। Shared-State Lab में 20,000 locked increments Manager dict के साथ लगभग 6.5 s लेते थे, पर Value के साथ लगभग 0.05 s — 100× से ज़्यादा धीमा। Manager को सुविधा के लिए मध्यम दर पर लीजिए, तेज़ loops में नहीं।
对 Manager 对象的每次读写都要往返一次服务器进程。在共享状态实验室中,20,000 次加锁自增用 Manager 字典约需 6.5 秒,而用 Value 只需约 0.05 秒 — 慢了 100 多倍。Manager 适合在访问频率适中时图个方便,不要用在热点循环里。
Shared Memory — The Modern shared_memory Module 🚄Shared Memory — आधुनिक shared_memory Module 🚄共享内存 — 现代的 shared_memory 模块 🚄
Added in Python 3.8, multiprocessing.shared_memory lets processes share a raw block of memory with no pickling. Together with NumPy, it is the fastest way to share large numeric arrays.
Python 3.8 में जुड़ा multiprocessing.shared_memory processes को बिना pickling के memory का एक कच्चा block बाँटने देता है। NumPy के साथ, बड़े numeric arrays बाँटने का यह सबसे तेज़ तरीक़ा है।
Python 3.8 新增的 multiprocessing.shared_memory 让进程无需 pickle 就能共享一块原始内存。配合 NumPy,它是共享大型数值数组最快的方式。
from multiprocessing import Process
from multiprocessing.shared_memory import SharedMemory
import numpy as np
def worker(shm_name, shape, dtype, start, end):
# Attach to the existing shared memory block by name
existing = SharedMemory(name=shm_name)
arr = np.ndarray(shape, dtype=dtype, buffer=existing.buf)
arr[start:end] *= 2 # change in place - no copy, no pickle
del arr # drop the view before closing
existing.close()
if __name__ == "__main__":
# Allocate a 1-million-element float64 array in shared memory
shape = (1_000_000,)
dtype = np.float64
nbytes = np.zeros(shape, dtype=dtype).nbytes
shm = SharedMemory(create=True, size=nbytes)
arr = np.ndarray(shape, dtype=dtype, buffer=shm.buf)
arr[:] = np.arange(1_000_000)
# Start 4 workers, each changing one slice
chunk = 250_000
procs = [Process(target=worker,
args=(shm.name, shape, dtype, i * chunk, (i + 1) * chunk))
for i in range(4)]
for p in procs: p.start()
for p in procs: p.join()
print("First 5 :", arr[:5])
print("Last 5 :", arr[-5:])
del arr
shm.close()
shm.unlink() # free the shared memory block
We added del arr before close(). A NumPy array built on shm.buf still points into that memory; closing the block while the array exists can raise BufferError: cannot close exported pointers exist. Delete (or stop using) the array first, then close(), and call unlink() once, in the process that created the block.
हमने close() से पहले del arr जोड़ा। shm.buf पर बना NumPy array अब भी उस memory की ओर इशारा करता है; array रहते block बंद करने पर BufferError: cannot close exported pointers exist आ सकता है। पहले array हटाइए (या इस्तेमाल बंद कीजिए), फिर close(), और unlink() सिर्फ़ एक बार उस process में बुलाइए जिसने block बनाया।
我们在 close() 之前加了 del arr。基于 shm.buf 构建的 NumPy 数组仍然指向那块内存;数组还在时关闭内存块,可能抛出 BufferError: cannot close exported pointers exist。先删除(或停止使用)数组,再 close(),并且只在创建该内存块的进程中调用一次 unlink()。
ShareableList — a simpler alternativeShareableList — एक सरल विकल्पShareableList — 更简单的替代方案
Here is the original example, run for real:
यह मूल example है, असल में चलाया गया:
这是原始示例的真实运行结果:
from multiprocessing import Process
from multiprocessing.shared_memory import ShareableList
def worker(name):
sl = ShareableList(name=name)
sl[0] = "modified by child"
sl[1] += 100
sl.shm.close()
if __name__ == "__main__":
sl = ShareableList(["hello", 42, 3.14, True])
print("Before:", list(sl))
p = Process(target=worker, args=(sl.shm.name,))
p.start(); p.join()
print("After :", list(sl))
sl.shm.close(); sl.shm.unlink()
The original showed After : ['modified by child', 142, ...]. The real run fails: the child raises ValueError: bytes/str item exceeds available storage, and the list is unchanged. A ShareableList has a fixed size: each slot gets just enough bytes for its first value, and "hello" left no room for "modified by child". The fix is to reserve enough space when you create it:
मूल ने After : ['modified by child', 142, ...] दिखाया। असली run fail होता है: child ValueError: bytes/str item exceeds available storage raise करता है, और list नहीं बदलती। ShareableList का size तय होता है: हर slot को उसकी पहली value जितने ही bytes मिलते हैं, और "hello" ने "modified by child" के लिए जगह नहीं छोड़ी। इलाज है बनाते समय पर्याप्त जगह reserve करना:
原文显示 After : ['modified by child', 142, ...]。真实运行却失败了:子进程抛出 ValueError: bytes/str item exceeds available storage,列表没有任何变化。ShareableList 的 大小是固定的:每个槽位只分配了刚好装下第一个值的字节数,"hello" 没有给 "modified by child" 留下空间。修复方法是在创建时预留足够的空间:
from multiprocessing import Process
from multiprocessing.shared_memory import ShareableList
def worker(name):
sl = ShareableList(name=name)
sl[0] = "modified by child" # fits: 32 characters were reserved
sl[1] += 100
sl.shm.close()
if __name__ == "__main__":
sl = ShareableList([" " * 32, 42, 3.14, True]) # reserve room for the string
sl[0] = "hello"
print("Before:", list(sl))
p = Process(target=worker, args=(sl.shm.name,))
p.start(); p.join()
print("After :", list(sl))
sl.shm.close(); sl.shm.unlink()
The original said passing a 100 MB array through a Queue takes "~2 seconds" and through shared memory "~0.001 seconds". The IPC Cost Lab (Section 10) measured it on our machine: Queue about 0.41 s, Pipe about 0.32 s, shared_memory about 0.018 s — and that last number includes the child adding up all 100 MB. Still more than 20× faster, but the exact numbers depend on your machine.
मूल में कहा गया कि 100 MB array Queue से भेजने में "~2 seconds" और shared memory से "~0.001 seconds" लगते हैं। IPC Cost Lab (Section 10) ने इसे हमारी machine पर मापा: Queue लगभग 0.41 s, Pipe लगभग 0.32 s, shared_memory लगभग 0.018 s — और आख़िरी number में child का पूरे 100 MB को जोड़ना भी शामिल है। फिर भी 20× से ज़्यादा तेज़, पर सटीक numbers आपकी machine पर निर्भर हैं।
原文说通过 Queue 传递 100 MB 数组需"约 2 秒",通过共享内存需"约 0.001 秒"。进程间通信开销实验室(第 10 节)在我们的机器上测得:Queue 约 0.41 秒,Pipe 约 0.32 秒,shared_memory 约 0.018 秒 — 最后这个数字还包含了子进程对全部 100 MB 求和的时间。仍然快了 20 倍以上,但具体数字取决于你的机器。
Synchronisation Primitives 🚦Synchronisation Primitives 🚦同步原语 🚦
Just like threads, processes sometimes need to coordinate. multiprocessing provides process-safe versions of the usual tools.
Threads की तरह processes को भी कभी-कभी तालमेल चाहिए। multiprocessing आम औज़ारों के process-safe versions देता है।
和线程一样,进程有时也需要协调。multiprocessing 提供了常用工具的进程安全版本。
with lock:.with lock: लीजिए।with lock:。event.set(); every process waiting in event.wait() continues. Good for "start now" or "please stop".event.set() बुलाता है; event.wait() में रुका हर process आगे बढ़ता है। "अब शुरू" या "कृपया रुको" के लिए अच्छा।event.set(),所有在 event.wait() 中等待的进程都继续执行。适合"开始"或"请停止"信号。barrier.wait() before any can continue. Good for phases: everyone finishes phase 1 before phase 2.barrier.wait() तक पहुँचना होता है। Phases के लिए अच्छा: phase 2 से पहले सब phase 1 पूरा करें।barrier.wait() 后,才能有任何一个继续。适合分阶段的工作:所有人完成第一阶段后再开始第二阶段。task_done() and join() — the process version of queue.Queue for the poison-pill pattern.task_done() और join() — poison-pill pattern के लिए queue.Queue का process version।task_done() 和 join() — 相当于用于毒丸模式的 queue.Queue 的进程版本。from multiprocessing import Process, Event, Barrier
import time
import random
def worker(worker_id, start_event, barrier):
start_event.wait() # wait for the green light
print(f"[W{worker_id}] phase 1 starting")
time.sleep(random.uniform(0.5, 2))
print(f"[W{worker_id}] phase 1 done, waiting at barrier")
barrier.wait() # wait for all workers
print(f"[W{worker_id}] phase 2 starting")
if __name__ == "__main__":
N = 3
start_event = Event()
barrier = Barrier(N)
procs = [Process(target=worker, args=(i, start_event, barrier))
for i in range(N)]
for p in procs: p.start()
time.sleep(1) # let workers set up
print("All workers ready. GO!")
start_event.set() # release all at once
for p in procs: p.join()
No worker started phase 1 before "GO!", and no worker started phase 2 until all three had finished phase 1 — even though their phase-1 times were random.
"GO!" से पहले किसी worker ने phase 1 शुरू नहीं किया, और तीनों के phase 1 पूरा करने तक किसी ने phase 2 शुरू नहीं किया — भले ही उनके phase-1 समय random थे।
在"GO!"之前没有任何工作进程开始第一阶段;在三个都完成第一阶段之前,也没有任何一个开始第二阶段 — 即使它们第一阶段的耗时是随机的。
Real-World Example — Parallel Image Processing 🖼️असली उदाहरण — Parallel Image Processing 🖼️实战示例 — 并行图像处理 🖼️
A classic use of multiprocessing: apply a heavy filter to many images. We generated 24 noisy 1024×768 JPEG test images first.
Multiprocessing का एक classic इस्तेमाल: कई images पर भारी filter लगाना। हमने पहले 24 noisy 1024×768 JPEG test images बनाईं।
多进程的经典用法:对大量图片应用繁重的滤镜。我们先生成了 24 张 1024×768 的带噪点 JPEG 测试图片。
from multiprocessing import Pool, cpu_count
from PIL import Image, ImageFilter
from pathlib import Path
import time
INPUT_DIR = Path("input_images")
OUTPUT_DIR = Path("output_images")
OUTPUT_DIR.mkdir(exist_ok=True)
def process_image(input_path):
"""Load, apply an expensive filter, save."""
img = Image.open(input_path)
img = img.filter(ImageFilter.GaussianBlur(radius=5)) # CPU-heavy filter chain
img = img.filter(ImageFilter.EDGE_ENHANCE_MORE)
img = img.convert("RGB")
output_path = OUTPUT_DIR / input_path.name
img.save(output_path, quality=85)
return input_path.name
if __name__ == "__main__":
images = sorted(INPUT_DIR.glob("*.jpg"))
print(f"Processing {len(images)} images on {cpu_count()} cores")
start = time.perf_counter()
for path in images: # baseline: one after another
process_image(path)
print(f"Sequential: {time.perf_counter() - start:.2f}s")
start = time.perf_counter()
with Pool(processes=cpu_count()) as pool:
# imap_unordered streams results, chunksize batches for efficiency
for i, name in enumerate(pool.imap_unordered(process_image, images, chunksize=4), 1):
print(f" [{i}/{len(images)}] {name}")
print(f"Pool({cpu_count()}): {time.perf_counter() - start:.2f}s")
Checking the claim: the same job with threadsदावे की जाँच: वही काम threads से验证说法:用线程做同样的工作
from concurrent.futures import ThreadPoolExecutor
from PIL import Image, ImageFilter
from pathlib import Path
import time
INPUT_DIR, OUTPUT_DIR = Path("input_images"), Path("output_images")
OUTPUT_DIR.mkdir(exist_ok=True)
def process_image(input_path): # the same function as before
img = Image.open(input_path)
img = img.filter(ImageFilter.GaussianBlur(radius=5))
img = img.filter(ImageFilter.EDGE_ENHANCE_MORE)
img.convert("RGB").save(OUTPUT_DIR / input_path.name, quality=85)
images = sorted(INPUT_DIR.glob("*.jpg"))
start = time.perf_counter()
with ThreadPoolExecutor(max_workers=2) as ex: # THREADS, not processes
list(ex.map(process_image, images))
print(f"2 threads: {time.perf_counter() - start:.2f}s")
The original said "threads would be crippled by the GIL" for this job. Our real runs say otherwise: 2 threads took 0.73 s, almost the same as the 2-process Pool (0.67 s), both about 2× faster than one after another (1.42 s). Pillow does its heavy filtering in C code that releases the GIL, so threads run it in parallel. Processes win when the heavy part is pure Python code. Always measure. (The original's "500 images, 7× on 8 cores" could not be checked here; on 2 cores the best possible is about 2×.)
मूल में कहा गया कि इस काम में "threads GIL से अपंग हो जाएँगे"। हमारे असली runs कुछ और कहते हैं: 2 threads ने 0.73 s लिए, लगभग 2-process Pool जितने (0.67 s), दोनों एक-के-बाद-एक (1.42 s) से लगभग 2× तेज़। Pillow अपना भारी filtering C code में करता है जो GIL छोड़ देता है, इसलिए threads उसे parallel चलाते हैं। Processes तब जीतते हैं जब भारी हिस्सा pure Python code हो। हमेशा मापिए। (मूल का "500 images, 8 cores पर 7×" यहाँ जाँचा नहीं जा सका; 2 cores पर ज़्यादा से ज़्यादा लगभग 2× संभव है।)
原文说在这项工作中"线程会被 GIL 拖垮"。我们的真实运行结果并非如此:2 个线程用时 0.73 秒,与 2 进程的 Pool(0.67 秒)几乎相同,两者都比依次执行(1.42 秒)快约 2 倍。Pillow 在 C 代码中完成繁重的滤镜计算,这些代码 会释放 GIL,所以线程能并行运行它们。只有当繁重部分是 纯 Python 代码时,进程才会胜出。一定要实测。(原文"500 张图片,8 核快 7 倍"在这里无法验证;在 2 核上最多约快 2 倍。)
Real-World Example — Parallel Aggregation With Shared Memory 📊असली उदाहरण — Shared Memory के साथ Parallel Aggregation 📊实战示例 — 用共享内存做并行聚合 📊
Compute the mean of every row of a big NumPy array using all cores, without copying the data to each worker. The input lives in shared_memory; the output is a small shared Array. We added one line at the end: the same job as a single NumPy call.
एक बड़े NumPy array की हर row का mean सभी cores से निकालना, हर worker को data copy किए बिना। Input shared_memory में है; output एक छोटी साझा Array है। हमने आख़िर में एक line जोड़ी: वही काम एक अकेली NumPy call से।
用所有核心计算一个大型 NumPy 数组每一行的平均值,而不把数据复制给每个工作进程。输入放在 shared_memory 中;输出是一个小的共享 Array。我们在末尾加了一行:用一次 NumPy 调用完成同样的工作。
from multiprocessing import Process, Array, cpu_count
from multiprocessing.shared_memory import SharedMemory
import numpy as np
import time
def worker(shm_name, shape, dtype, out_arr, start_row, end_row):
shm = SharedMemory(name=shm_name)
data = np.ndarray(shape, dtype=dtype, buffer=shm.buf)
for i in range(start_row, end_row): # row means for our slice
out_arr[i] = data[i].mean()
del data
shm.close()
if __name__ == "__main__":
# A 20,000 x 1,000 dataset (~160 MB of float64)
N_ROWS, N_COLS = 20_000, 1_000
data = np.random.rand(N_ROWS, N_COLS)
# Move it into shared memory
shm = SharedMemory(create=True, size=data.nbytes)
shared = np.ndarray(data.shape, dtype=data.dtype, buffer=shm.buf)
shared[:] = data
results = Array('d', N_ROWS) # shared output - one mean per row
n_workers = cpu_count() # split the rows across cores
chunk = N_ROWS // n_workers
start = time.perf_counter()
procs = []
for i in range(n_workers):
s = i * chunk
e = N_ROWS if i == n_workers - 1 else (i + 1) * chunk
p = Process(target=worker,
args=(shm.name, data.shape, data.dtype, results, s, e))
procs.append(p); p.start()
for p in procs: p.join()
print(f"Parallel: {time.perf_counter() - start:.3f}s using {n_workers} workers")
print(f"First 5 row means: {[round(x, 4) for x in results[:5]]}")
start = time.perf_counter() # baseline: ONE vectorised NumPy call
means = data.mean(axis=1)
print(f"NumPy, 1 process: {time.perf_counter() - start:.3f}s same answer? {np.allclose(means, results[:])}")
del shared
shm.close(); shm.unlink()
We reduced the data from 100,000 rows (~800 MB) to 20,000 rows (~160 MB) to fit our test machine. The parallel version took 0.080 s; one data.mean(axis=1) call in a single process took 0.022 s — about 3.6× faster, with the same answer. NumPy already runs this loop in fast C, while the workers loop row by row in Python and lock the shared Array on every write. Lesson: before splitting work across processes, check whether a vectorised NumPy call already solves it. Parallel shared memory pays off when each piece of work is heavy.
हमने data को 100,000 rows (~800 MB) से घटाकर 20,000 rows (~160 MB) किया ताकि यह हमारी test machine पर चले। Parallel version ने 0.080 s लिए; एक process में एक data.mean(axis=1) call ने 0.022 s — लगभग 3.6× तेज़, वही जवाब। NumPy यह loop पहले से तेज़ C में चलाता है, जबकि workers Python में row-दर-row loop करते हैं और हर write पर साझा Array को lock करते हैं। सबक़: processes में काम बाँटने से पहले जाँचिए कि कोई vectorised NumPy call पहले से इसे हल करती है या नहीं। Parallel shared memory तब फ़ायदा देती है जब काम का हर हिस्सा भारी हो।
为了适应我们的测试机器,我们把数据从 100,000 行(约 800 MB)缩小到 20,000 行(约 160 MB)。并行版本用时 0.080 秒;在单个进程中调用一次 data.mean(axis=1) 只用了 0.022 秒 — 快约 3.6 倍,答案相同。NumPy 已经用高速的 C 代码执行这个循环,而工作进程却在 Python 中逐行循环,并且每次写入都要锁住共享的 Array。教训:在把工作拆分到多个进程之前,先看看一次向量化的 NumPy 调用是否已经能解决问题。只有当每块工作都很繁重时,并行共享内存才划算。
Common Pitfalls and Fixes 🩺आम ग़लतियाँ और उनके Fix 🩺常见陷阱与修复 🩺
if __name__ == "__main__" → with spawn/forkserver every child re-runs your top-level code and fails with a RuntimeError. Always guard process creation.if __name__ == "__main__" ग़ायब → spawn/forkserver में हर child आपका top-level code फिर चलाता है और RuntimeError से fail होता है। Process बनाने को हमेशा guard कीजिए।if __name__ == "__main__" → 使用 spawn/forkserver 时,每个子进程都会重新运行你的顶层代码,并以 RuntimeError 失败。启动进程的代码一定要加保护。PicklingError. Move the function to module level.PicklingError। Function को module level पर ले जाइए।PicklingError。把函数移到模块级。close() and unlink() on shared memory → the block can stay in /dev/shm. Python's resource tracker usually removes leftovers at exit with a warning, but after a crash they can stay until reboot.close() और unlink() न बुलाना → block /dev/shm में रह सकता है। Python का resource tracker आम तौर पर exit पर warning के साथ बचा हुआ हटा देता है, पर crash के बाद वे reboot तक रह सकते हैं।close() 和 unlink() → 内存块可能留在 /dev/shm 中。Python 的资源跟踪器通常会在退出时清理残留并给出警告,但在崩溃后它们可能一直留到重启。Pool.map → every task pays the pickling cost. Use shared_memory or send less data.Pool.map को बड़े objects देना → हर task pickling की क़ीमत चुकाता है। shared_memory लीजिए या कम data भेजिए।Pool.map → 每个任务都要付出序列化的代价。使用 shared_memory 或减少数据传输。p.join(), or use with Pool() as pool:.p.join() बुलाइए, या with Pool() as pool: लीजिए।p.join(),或使用 with Pool() as pool:。apply_async tasks vanish → the exception is only raised when you call .get(). Always call it, or pass an error_callback.apply_async tasks के errors ग़ायब → exception तभी raise होता है जब आप .get() बुलाते हैं। हमेशा बुलाइए, या error_callback दीजिए।apply_async 任务中的错误消失了 → 只有调用 .get() 时才会抛出异常。一定要调用它,或传入 error_callback。p.join() hangs — even with no maxsize. Read (get()) the results first, then join.p.join() अटकता है — maxsize के बिना भी। पहले results पढ़िए (get()), फिर join कीजिए।p.join() 会卡住 — 即使没有设置 maxsize。先读取(get())结果,再 join。The original described Bug 8 as a child blocked on put() to a full Queue. The Bug Doctor below shows the real case with an unbounded Queue: the child put 10 MB, and after join(timeout=2) it was still alive. Its feeder thread must hand all the bytes to the pipe before the process can end, and the pipe only drains when someone reads. Python's docs call this "joining processes that use queues".
मूल ने Bug 8 को भरी Queue पर put() में अटका child बताया। नीचे Bug Doctor एक असीमित Queue के साथ असली case दिखाता है: child ने 10 MB डाले, और join(timeout=2) के बाद भी वह ज़िंदा था। Process ख़त्म होने से पहले उसके feeder thread को सारे bytes pipe को देने होते हैं, और pipe तभी ख़ाली होता है जब कोई पढ़ता है। Python के docs इसे "joining processes that use queues" कहते हैं।
原文把 Bug 8 描述为子进程在向一个 满 队列 put() 时阻塞。下面的 Bug 医生展示了一个使用 无界 队列的真实案例:子进程放入了 10 MB,join(timeout=2) 之后它仍然活着。它的馈送线程必须把所有字节交给管道后进程才能结束,而管道只有在有人读取时才会被清空。Python 文档称之为"joining processes that use queues"。
Wrap your pool in with Pool() as pool:, catch errors inside each task and return them as data, and call .get() on async results so exceptions surface. This removes most multiprocessing bugs. Here is a runnable version (the original used undefined process and items):
अपने pool को with Pool() as pool: में रखिए, हर task के अंदर errors पकड़कर उन्हें data के रूप में लौटाइए, और async results पर .get() बुलाइए ताकि exceptions सामने आएँ। इससे ज़्यादातर multiprocessing bugs हट जाते हैं। यह चलने वाला version है (मूल में process और items define नहीं थे):
把线程池包在 with Pool() as pool: 中,在每个任务内部捕获错误并作为数据返回,并对异步结果调用 .get() 让异常暴露出来。这能消除大多数多进程 bug。下面是可直接运行的版本(原文中的 process 和 items 未定义):
from multiprocessing import Pool
def process(item): # your real work
if item % 4 == 0:
raise ValueError(f"bad item {item}")
return item * 10
def safe_task(item):
try:
return {"ok": True, "result": process(item)}
except Exception as e:
return {"ok": False, "error": repr(e), "item": item}
if __name__ == "__main__":
items = range(1, 11)
with Pool(processes=2) as pool:
results = pool.map(safe_task, items)
ok = [r for r in results if r["ok"]]
fail = [r for r in results if not r["ok"]]
print(f"{len(ok)} succeeded, {len(fail)} failed")
print("failures:", [(r["item"], r["error"]) for r in fail])
🩺 Simulator: Bug Doctor🩺 Simulator: Bug Doctor🩺 模拟器:Bug 医生
Six real multiprocessing mistakes. Buggy code on the left, fixed code on the right, changed lines marked. Every output is from a real run.
छह असली multiprocessing ग़लतियाँ। बाईं ओर buggy code, दाईं ओर ठीक किया हुआ, बदली lines चिह्नित। हर output असली run से है।
六个真实的多进程错误。左边是有 bug 的代码,右边是修正后的代码,改动的行已标出。每个输出都来自真实运行。
Choosing the Right Tool — Decision Table 🧭सही औज़ार चुनना — फ़ैसले की Table 🧭选择合适的工具 — 决策表 🧭
| Needज़रूरत需求 | Useलीजिए使用 | Whyक्यों原因 |
|---|---|---|
| Spread independent tasks over workersअलग-अलग tasks workers में बाँटना把独立任务分给工作进程 | Pool.map / imap_unordered | Simplest; everything built inसबसे सरल; सब कुछ अंदर最简单;一切都内置好了 |
| Streaming producer-consumerलगातार producer-consumer流式生产者-消费者 | Queue + Process | Many-to-many, safeकई-से-कई, सुरक्षित多对多,安全 |
| Fast two-way parent-child channelतेज़ दो-तरफ़ा parent-child channel快速的父子双向通道 | Pipe | Less overhead than QueueQueue से कम overhead开销比 Queue 小 |
| Shared counter or small numeric arrayसाझा counter या छोटी numeric array共享计数器或小型数值数组 | Value / Array | Built-in lock, C speedअंदर lock, C की गति内置锁,C 的速度 |
| Shared dict/list of Python objectsPython objects की साझा dict/list共享 Python 对象的字典/列表 | Manager().dict() | Flexible, but slow (each access is a request)लचीला, पर धीमा (हर पहुँच एक request)灵活但慢(每次访问都是一次请求) |
| Large NumPy arrays or byte buffersबड़े NumPy arrays या byte buffers大型 NumPy 数组或字节缓冲区 | shared_memory + NumPy | Zero copy, very fastZero copy, बहुत तेज़零拷贝,非常快 |
| Start/stop all workers togetherसभी workers को एक साथ शुरू/बंद करना同时启动/停止所有工作进程 | Event | One signal to everyoneसबको एक signal一个信号通知所有人 |
| Move between phases togetherPhases में एक साथ आगे बढ़ना一起进入下一阶段 | Barrier | All must arrive before anyone goes onकिसी के बढ़ने से पहले सबको पहुँचना होगा所有人到齐后才继续 |
| Limit access to a resourceकिसी resource तक पहुँच सीमित करना限制对某资源的访问 | Semaphore | Cap concurrent DB or API callsएक साथ DB या API calls सीमित करें限制并发的数据库或 API 调用 |
Golden Rules and Quiz 🏆सुनहरे नियम और Quiz 🏆黄金法则与小测验 🏆
if __name__ == "__main__":. With spawn (Windows, macOS) and forkserver (Linux default since 3.14), children re-import your script — without the guard they fail with a RuntimeError.if __name__ == "__main__": के नीचे रखिए। Spawn (Windows, macOS) और forkserver (3.14 से Linux default) में children आपकी script फिर import करते हैं — guard के बिना वे RuntimeError से fail होते हैं।if __name__ == "__main__": 之下。使用 spawn(Windows、macOS)和 forkserver(3.14 起 Linux 默认)时,子进程会重新导入你的脚本 — 没有保护就会以 RuntimeError 失败。Pool and imap_unordered: results stream in as they finish, with very little code.Pool और imap_unordered से शुरू कीजिए: results ख़त्म होते ही आते हैं, बहुत कम code में।Pool 和 imap_unordered:结果一完成就流式返回,代码很少。chunksize for many small tasks in imap/imap_unordered (their default is 1). map picks a chunksize for you. With a few large tasks, keep chunks small so one big task does not hold up others.imap/imap_unordered में बहुत छोटे tasks के लिए chunksize set कीजिए (उनका default 1 है)। map आपके लिए chunksize चुनता है। कुछ बड़े tasks हों तो chunks छोटे रखिए ताकि एक बड़ा task दूसरों को न रोके।imap/imap_unordered 中处理大量小任务时设置 chunksize(它们的默认值是 1)。map 会替你选择 chunksize。如果只有少量大任务,就让分块小一些,避免一个大任务拖住其他任务。shared_memory instead of pickling through a Queue. In our test, 100 MB took about 0.4 s through a Queue and about 0.02 s with shared memory.shared_memory लीजिए। हमारे test में 100 MB Queue से लगभग 0.4 s और shared memory से लगभग 0.02 s में गया।shared_memory,而不是通过 Queue 进行 pickle。在我们的测试中,100 MB 通过 Queue 约需 0.4 秒,用共享内存约 0.02 秒。close() on shared memory in every process, and unlink() once in the creator. Drop NumPy views before closing.close() बुलाइए, और बनाने वाले में एक बार unlink()। बंद करने से पहले NumPy views हटाइए।close(),并在创建者中调用一次 unlink()。关闭前先删除 NumPy 视图。join() would wait for ever.join() हमेशा इंतज़ार करेगा।join() 会永远等待。Pool.map raises one exception and you lose the other results — and apply_async errors stay hidden until .get().Pool.map एक exception raise करता है और बाक़ी results खो जाते हैं — और apply_async के errors .get() तक छिपे रहते हैं।Pool.map 会抛出一个异常,你会丢掉其他结果 — 而 apply_async 的错误在调用 .get() 之前一直隐藏着。daemon=True before start(), only for background workers that should end with the parent. Daemons are terminated without clean-up and cannot start children of their own.daemon=True start() से पहले set कीजिए, सिर्फ़ उन background workers के लिए जो parent के साथ ख़त्म होने चाहिए। Daemons बिना सफ़ाई के बंद होते हैं और अपने children शुरू नहीं कर सकते।start() 之前设置 daemon=True,只用于应随父进程一起结束的后台工作者。守护进程被终止时不做清理,也不能启动自己的子进程。Quick Quiz — 8 Questionsछोटा Quiz — 8 सवाल小测验 — 8 道题
Each process has its own interpreter, its own GIL and its own memory, so processes run Python code in true parallel — up to the number of cores you have. The price is that data must be pickled and copied between them, or placed in shared memory. Guard process creation with if __name__ == "__main__":, use Pool for batches, Queue or Pipe for messages, Value/Array, Manager or shared_memory for shared state, and always measure — sometimes threads or a single NumPy call are faster.
हर process का अपना interpreter, अपना GIL और अपनी memory होती है, इसलिए processes Python code सच में parallel चलाते हैं — आपके cores की संख्या तक। क़ीमत यह है कि data को उनके बीच pickle और copy करना पड़ता है, या shared memory में रखना पड़ता है। Process बनाने को if __name__ == "__main__": से guard कीजिए, batches के लिए Pool, संदेशों के लिए Queue या Pipe, साझा state के लिए Value/Array, Manager या shared_memory लीजिए, और हमेशा मापिए — कभी-कभी threads या एक अकेली NumPy call ज़्यादा तेज़ होती है।
每个进程都有自己的解释器、自己的 GIL 和自己的内存,所以进程能真正并行地运行 Python 代码 — 最多与你的核心数相同。代价是数据必须在进程之间 pickle 并复制,或者放进共享内存。用 if __name__ == "__main__": 保护进程创建,批处理用 Pool,传消息用 Queue 或 Pipe,共享状态用 Value/Array、Manager 或 shared_memory,并且一定要测量 — 有时线程或一次 NumPy 调用反而更快。