Python Advance 📂 Threads · 1 of 4 280 min read

Python Threading — Thread Class, start() vs run(), and Concurrent Programming Explained

Master Python threading from scratch — understand what a thread really is, the crucial difference between the legacy _thread module and modern threading module, how to create threads using both function and class-based approaches, why start() launches concurrency but run() destroys it, plus daemon threads, join(), passing arguments, and real-world examples like parallel downloads. Includes SVG diagrams.

🌐
SectionSection第 01 节

The Story That Explains Threads 🍽️Threads समझाने वाली कहानी 🍽️一个讲清线程的故事 🍽️

One Waiter, Many Tables — Using the Waiting Timeएक Waiter, कई Tables — इंतज़ार के समय का इस्तेमाल一个服务员,多张餐桌 — 利用等待时间
Imagine a restaurant with just one waiter. Table 1 orders pasta. The waiter takes the order to the kitchen, and the chef says "10 minutes". A naive waiter stands by the kitchen door for 10 minutes doing nothing, serves Table 1, and only then walks to Table 2. Three tables take 30 minutes.

A smart waiter does not stand and wait. He drops the order in the kitchen and goes straight to Table 2, then Table 3, then refills water at Table 4 — using the 10 minutes of cooking time to serve everyone else. When Table 1's pasta is ready, he brings it. Same single waiter, but three tables are served in about 12 minutes instead of 30.

That is Python threading: one worker that switches between many tasks while some of them are waiting on something slow — a network reply, a database query, a file read, a subprocess. It is not five cooks working at once; it is one smart waiter turning wasted waiting time into useful work.
एक रेस्टोरेंट की कल्पना कीजिए जिसमें सिर्फ़ एक waiter है। Table 1 पास्ता मँगाता है। Waiter order kitchen ले जाता है, और chef कहता है "10 मिनट"। भोला waiter 10 मिनट kitchen के दरवाज़े पर कुछ किए बिना खड़ा रहता है, Table 1 को परोसता है, और तभी Table 2 पर जाता है। तीन tables में 30 मिनट लगते हैं।

समझदार waiter खड़े होकर इंतज़ार नहीं करता। वह order kitchen में छोड़कर सीधे Table 2 पर जाता है, फिर Table 3, फिर Table 4 पर पानी भरता है — खाना पकने के 10 मिनट बाक़ी सबको परोसने में लगाता है। Table 1 का पास्ता तैयार होते ही वह ले आता है। वही एक waiter, पर तीन tables 30 की जगह लगभग 12 मिनट में।

यही Python threading है: एक worker जो कई कामों के बीच बदलता रहता है जब उनमें से कुछ किसी धीमी चीज़ का इंतज़ार कर रहे हों — network का जवाब, database query, file read, subprocess। यह पाँच cooks का एक साथ काम करना नहीं है; यह एक समझदार waiter है जो बर्बाद होने वाले इंतज़ार के समय को काम में बदल देता है।
想象一家只有一个服务员的餐厅。1 号桌点了意面。服务员把订单送到厨房,厨师说"10 分钟"。笨服务员会在厨房门口干站 10 分钟,然后给 1 号桌上菜,之后才走去 2 号桌。三张桌子要花 30 分钟。

聪明的服务员不会干等。他把订单放进厨房,马上去 2 号桌,再去 3 号桌,再给 4 号桌加水 — 用做菜的这 10 分钟去服务其他人。1 号桌的意面一好,他就端过去。还是同一个服务员,三张桌子大约 12 分钟就服务完了,而不是 30 分钟。

这就是 Python 的多线程:一个工作者在多个任务之间切换,而其中一些任务正在 等待 某个慢操作 — 网络响应、数据库查询、文件读取、子进程。这不是五个厨师同时干活,而是一个聪明的服务员把浪费的等待时间变成了有用的工作。

A process is a running program with its own memory. A thread is one line of execution inside a process. Every process has at least one thread — the main thread. Extra threads let one process juggle several tasks. In standard CPython only one thread runs Python code at a time; Python switches between them very often, so from the outside they look like they run together.

Process अपनी memory वाला एक चलता हुआ program है। Thread process के अंदर execution की एक धारा है। हर process में कम से कम एक thread होता है — main thread। अतिरिक्त threads से एक process कई काम सँभालता है। Standard CPython में एक समय में सिर्फ़ एक thread Python code चलाता है; Python उनके बीच बहुत जल्दी-जल्दी बदलता है, इसलिए बाहर से लगता है कि वे साथ चल रहे हैं।

进程 是一个拥有独立内存的运行中的程序。线程 是进程内部的一条执行路线。每个进程至少有一个线程 — 主线程。增加线程可以让一个进程同时应付多个任务。在标准的 CPython 中,同一时刻只有一个线程在执行 Python 代码;Python 在它们之间切换得非常频繁,所以从外面看它们像是在同时运行。

💡
The Core Insight — Threads Win Back Waiting Timeमूल बात — Threads इंतज़ार का समय वापस जीतते हैं核心要点 — 线程把等待时间夺回来

A thread that waits on I/O — a network reply, a disk read, a database query, time.sleep(), input() — is doing no CPU work. Python uses that idle moment to run another thread. Threads do not make your CPU compute faster; they stop your program from sitting idle. That is why threading gives big speedups for I/O-bound programs and almost none for pure-math CPU-bound programs — you will measure both in Section 02.

I/O का इंतज़ार करता thread — network reply, disk read, database query, time.sleep(), input() — कोई CPU काम नहीं कर रहा। Python उस ख़ाली पल में दूसरा thread चलाता है। Threads आपके CPU को तेज़ नहीं बनाते; वे आपके program को ख़ाली बैठने से रोकते हैं। इसीलिए threading I/O-bound programs में बड़ी speedup देती है और सिर्फ़ गणित वाले CPU-bound programs में लगभग कुछ नहीं — दोनों आप Section 02 में नापेंगे।

等待 I/O 的线程 — 网络响应、磁盘读取、数据库查询、time.sleep()、input() — 并没有在做 CPU 工作。Python 会利用这段空闲去运行另一个线程。线程不会让 CPU 算得更快;它们只是不让程序闲坐着。这就是为什么多线程对 I/O 密集型 程序有巨大加速,而对纯数学的 CPU 密集型 程序几乎没有效果 — 你将在第 02 节亲自测量两者。

🔧
Fixed: "Only One Is Running" — and Whyसुधार: "सिर्फ़ एक चल रहा है" — और क्यों已修正:"同一时刻只有一个在运行"— 以及原因

The original said the interpreter switches threads and "at any single instant, only one is running". Two small corrections. (1) It is the operating system that schedules threads; what allows only one thread to run Python code at a time in standard CPython is a lock called the GIL (covered in an upcoming tutorial). (2) Threads that are waiting — on sleep(), the network or the disk — are not using that lock at all, and that is exactly why the waits overlap.

मूल में कहा गया कि interpreter threads बदलता है और "किसी भी पल में सिर्फ़ एक चल रहा होता है"। दो छोटे सुधार। (1) Threads को operating system schedule करता है; standard CPython में एक समय में सिर्फ़ एक thread को Python code चलाने देने वाला एक lock है जिसे GIL कहते हैं (एक आने वाले tutorial में)। (2) इंतज़ार करने वाले threads — sleep(), network या disk पर — उस lock का इस्तेमाल ही नहीं करते, और इसी वजह से इंतज़ार एक-दूसरे पर चढ़ जाते हैं।

原文说解释器在线程之间切换,并且"在任何一个瞬间只有一个在运行"。两个小修正:(1) 调度线程的是操作系统;在标准 CPython 中,只允许一个线程同时执行 Python 代码 的,是一把叫做 GIL 的锁(将在 后续教程 中介绍)。(2) 正在等待的线程 — 等 sleep()、网络或磁盘 — 根本不占用这把锁,这正是等待时间能够重叠的原因。

▶️
Six Simulators and a Quizछह Simulators और एक Quiz六个模拟器和一个小测验

Look for the interactive boxes: Sequential vs Threads (Section 02), start() vs run() (06), the Lifecycle Lab (10), join(timeout) Lab and Loop Patterns (11), and the Bug Doctor (12). All timings and outputs come from real runs.

Interactive डिब्बे खोजें: Sequential बनाम Threads (Section 02), start() बनाम run() (06), Lifecycle Lab (10), join(timeout) Lab और Loop Patterns (11), और Bug Doctor (12)। सारी timings और outputs असली runs से हैं।

请留意这些互动框:顺序 vs 线程(第 02 节)、start() vs run()(06)、生命周期实验室(10)、join(timeout) 实验室 和 循环写法(11),以及 Bug 医生(12)。所有计时和输出都来自真实运行。


SectionSection第 02 节

Single-Threaded vs Multi-Threaded — The Visual Difference ⏱️Single-Threaded बनाम Multi-Threaded — दिखने वाला फ़र्क़ ⏱️单线程 vs 多线程 — 直观的差别 ⏱️

Figure 1 — Sequential vs Concurrent Executionचित्र 1 — क्रम से बनाम एक साथ图 1 — 顺序执行 vs 并发执行
Sequential vs concurrent executionSINGLE-THREADED — 15 sSINGLE-THREADED — 15 s单线程 — 15 秒Task A5sTask B6sTask C4sMULTI-THREADED — 6 sMULTI-THREADED — 6 s多线程 — 6 秒Thread A5sThread B6sThread C4s↑ all done at 6 s0s5s10s15sThe waits overlap — total time = the slowest task, not the sumइंतज़ार एक-दूसरे पर चढ़ जाते हैं — कुल समय = सबसे धीमा काम, जोड़ नहीं等待相互重叠 — 总时间 = 最慢的任务,而不是总和

Speedup 15 s → 6 s comes from overlapping I/O waits, not from parallel CPU work.

15 s → 6 s की speedup I/O इंतज़ारों के एक-दूसरे पर चढ़ने से आती है, parallel CPU काम से नहीं।

15 秒 → 6 秒的加速来自 I/O 等待的重叠,而不是并行的 CPU 计算。

⏱️ Simulator: Sequential vs Threads⏱️ Simulator: Sequential बनाम Threads⏱️ 模拟器:顺序 vs 线程

The same three tasks (A, B, C), run one after another and then in three threads. Choose I/O (each task sleeps — like waiting for a server) or CPU (each task does pure math). The bars are real start and end times from runs on our test machine.

वही तीन काम (A, B, C), पहले एक के बाद एक और फिर तीन threads में। I/O (हर काम sleep करता है — जैसे server का इंतज़ार) या CPU (हर काम सिर्फ़ गणित करता है) चुनिए। Bars हमारी test machine पर runs के असली शुरू और ख़त्म होने के समय हैं।

同样的三个任务(A、B、C),先依次运行,再放进三个线程运行。选择 I/O(每个任务都 sleep — 就像等待服务器)或 CPU(每个任务只做数学计算)。条形图是我们测试机上真实运行时记录的开始和结束时间。

Sequential vs ThreadsSequential बनाम Threads顺序 vs 线程real start / end timesअसली शुरू / ख़त्म समय真实的开始 / 结束时间
Kind of work:काम का प्रकार:任务类型:
One after anotherएक के बाद एक依次执行
Three threadsतीन threads三个线程

SectionSection第 03 节

_thread Module vs threading Module 🧰_thread Module बनाम threading Module 🧰_thread 模块 vs threading 模块 🧰

Python ships with two threading APIs. Knowing why matters — you will meet both in old code and in interview questions.

Python में दो threading APIs हैं। इसका कारण जानना ज़रूरी है — दोनों आपको पुराने code और interview सवालों में मिलेंगे।

Python 自带两套线程 API。了解其中原因很重要 — 你会在旧代码和面试题中遇到这两者。

🔌 _thread (low-level)_thread (low-level)_thread(底层)
FeatureFeature特性Statusस्थिति情况
Originशुरुआत起源Very old; called thread in Python 2बहुत पुराना; Python 2 में thread नाम非常古老;在 Python 2 中叫 thread
APIAPIAPISmall: start_new_thread, allocate_lock, get_ident, …छोटा: start_new_thread, allocate_lock, get_ident, …很小:start_new_thread、allocate_lock、get_ident 等
Named threadsनाम वाले threads线程命名Noनहीं不支持
join() / waitjoin() / इंतज़ारjoin() / 等待Build it yourselfख़ुद बनाइए需要自己实现
Use it?इस्तेमाल करें?要用吗?No — legacy code onlyनहीं — सिर्फ़ पुराना code不要 — 仅限旧代码
🛠️ threading (high-level)threading (high-level)threading(高层)
FeatureFeature特性Statusस्थिति情况
Originशुरुआत起源Since Python 1.5 — built on top of _threadPython 1.5 से — _thread के ऊपर बना自 Python 1.5 起 — 基于 _thread 构建
APIAPIAPIRich: Thread, Lock, Event, Semaphore, Timer, Barrierसमृद्ध: Thread, Lock, Event, Semaphore, Timer, Barrier丰富:Thread、Lock、Event、Semaphore、Timer、Barrier
Named threadsनाम वाले threads线程命名Yes — Thread(name="worker-1")हाँ — Thread(name="worker-1")支持 — Thread(name="worker-1")
join() / waitjoin() / इंतज़ारjoin() / 等待Built in, with a timeoutBuilt-in, timeout के साथ内置,支持超时
Use it?इस्तेमाल करें?要用吗?Yes — alwaysहाँ — हमेशा要 — 一律使用
🔧
Fixed: Origin and Size of the Two Modulesसुधार: दोनों Modules की शुरुआत और आकार已修正:两个模块的起源和规模

The original table said threading appeared in "Python 2.4+" and that _thread has "2 functions". In fact threading has been in Python since version 1.5 (the late 1990s), and _thread has more than a handful of functions (start_new_thread, allocate_lock, get_ident, exit, interrupt_main, stack_size and more). The advice is unchanged: use threading.

मूल table में कहा गया कि threading "Python 2.4+" में आया और _thread में "2 functions" हैं। असल में threading Python में version 1.5 (1990 के दशक के अंत) से है, और _thread में मुट्ठी भर से ज़्यादा functions हैं (start_new_thread, allocate_lock, get_ident, exit, interrupt_main, stack_size वग़ैरह)। सलाह वही है: threading लीजिए।

原表格说 threading 出现于"Python 2.4+",并说 _thread 只有"2 个函数"。实际上 threading 从 1.5 版(1990 年代末)起就存在了,而 _thread 也不止几个函数(start_new_thread、allocate_lock、get_ident、exit、interrupt_main、stack_size 等)。建议不变:请使用 threading。

# --- The old _thread way (avoid) ---
import _thread, time

def worker(name):
    print(f"{name} started")
    time.sleep(1)
    print(f"{name} done")

_thread.start_new_thread(worker, ("t1",))
time.sleep(2)      # have to sleep - no join() available
OUTPUT
t1 started t1 done
# --- The modern threading way (use this) ---
import threading, time

def worker(name):
    print(f"{name} started")
    time.sleep(1)
    print(f"{name} done")

t = threading.Thread(target=worker, args=("t1",), name="worker-1")
t.start()
t.join()           # proper synchronization
OUTPUT
t1 started t1 done
⚠️
Never Use _thread in New Codeनए Code में _thread कभी नहीं新代码中绝不要用 _thread

In Python 3 the module was renamed from thread to _thread. The leading underscore says "internal — do not use directly". Reach for threading every time.

Python 3 में module का नाम thread से _thread किया गया। आगे का underscore कहता है "अंदरूनी — सीधे इस्तेमाल न करें"। हर बार threading लीजिए।

在 Python 3 中,这个模块从 thread 改名为 _thread。开头的下划线表示"内部使用 — 不要直接用"。每次都请使用 threading。


SectionSection第 04 节

Creating Your First Thread 🚀अपना पहला Thread बनाना 🚀创建你的第一个线程 🚀

The simplest way: pass a target function to threading.Thread, then call .start(). Python creates a new OS thread, that thread calls your function, and control comes back to the main thread straight away.

सबसे आसान तरीक़ा: threading.Thread को एक target function दीजिए, फिर .start() बुलाइए। Python एक नया OS thread बनाता है, वह thread आपका function बुलाता है, और नियंत्रण तुरंत main thread पर लौट आता है।

最简单的方法:把一个 target 函数传给 threading.Thread,然后调用 .start()。Python 会创建一个新的操作系统线程,由它来调用你的函数,而控制权立即回到主线程。

import threading, time

def count_down(name, n):
    for i in range(n, 0, -1):
        print(f"{name}: {i}")
        time.sleep(1)
    print(f"{name}: BLAST OFF!")

# Create two threads that run concurrently
t1 = threading.Thread(target=count_down, args=("Rocket-A", 3))
t2 = threading.Thread(target=count_down, args=("Rocket-B", 3))

t1.start()                 # begins concurrently
t2.start()                 # begins concurrently
t1.join()                  # wait for t1 to finish
t2.join()                  # wait for t2 to finish
print("Both rockets launched.")
OUTPUT
Rocket-A: 3 Rocket-B: 3 Rocket-A: 2 Rocket-B: 2 Rocket-A: 1 Rocket-B: 1 Rocket-A: BLAST OFF! Rocket-B: BLAST OFF! Both rockets launched.
⚡
Notice the Interleavingआपस में घुले Outputs देखिए注意交错的输出

Both countdowns print in turns — proof they run concurrently. In this real run the order was A, B, A, B…, but that is not guaranteed: the operating system decides who runs next, so on another run B could print before A. If order matters, you need synchronisation (covered in an upcoming tutorial).

दोनों countdowns बारी-बारी print करते हैं — इस बात का सबूत कि वे एक साथ चल रहे हैं। इस असली run में क्रम A, B, A, B… था, पर इसकी गारंटी नहीं: अगला कौन चलेगा यह operating system तय करता है, इसलिए दूसरे run में B, A से पहले print कर सकता है। क्रम ज़रूरी हो तो synchronisation चाहिए (एक आने वाले tutorial में)।

两个倒计时轮流打印 — 证明它们在并发运行。在这次真实运行中顺序是 A、B、A、B…,但这 并不保证:由操作系统决定下一个谁运行,所以换一次运行,B 可能先于 A 打印。如果顺序重要,就需要同步机制(将在后续教程中介绍)。


SectionSection第 05 节

The Thread Class — Constructor and Attributes 🧩Thread Class — Constructor और Attributes 🧩Thread 类 — 构造参数与属性 🧩

ParameterParameter参数Purposeकाम作用Typical useआम इस्तेमाल典型用法
targetThe callable the thread will runThread जो callable चलाएगा线程要执行的可调用对象The function you want to runजो function चलाना है你要运行的函数
argsA tuple of positional argumentsPositional arguments का tuple位置参数组成的元组args=(url, timeout)
kwargsA dict of keyword argumentsKeyword arguments की dict关键字参数组成的字典kwargs={'verbose': True}
nameA human-readable labelपढ़ने लायक नाम便于阅读的名字Great for debugging and logsDebugging और logs के लिए बढ़िया便于调试和记录日志
groupReserved for the futureभविष्य के लिए आरक्षित保留给将来使用Always Noneहमेशा None总是 None
Method / propertyMethod / property方法 / 属性What it doesक्या करता है作用
t.start()Starts the thread — runs target in the NEW threadThread शुरू करता है — target को NEW thread में चलाता है启动线程 — 在新线程中运行 target
t.run()Runs target in the CURRENT thread (do not call it yourself)target को CURRENT thread में चलाता है (ख़ुद मत बुलाइए)在当前线程中运行 target(不要自己调用)
t.join(timeout=None)Waits until this thread finishes (or the timeout passes)इस thread के ख़त्म होने (या timeout बीतने) तक इंतज़ार等待该线程结束(或超时)
t.is_alive()True after start() and until run() endsstart() के बाद से run() ख़त्म होने तक True从 start() 之后到 run() 结束前为 True
t.nameThe thread's label (you can change it)Thread का नाम (बदल सकते हैं)线程的名字(可以修改)
t.identPython's thread identifier (an int; None before start)Python का thread identifier (int; start से पहले None)Python 的线程标识符(整数;启动前为 None)
t.native_idThe ID the operating system gave the thread (Python 3.8+)Operating system का दिया thread ID (Python 3.8+)操作系统分配给线程的 ID(Python 3.8+)
import threading, time

def work(seconds):
    time.sleep(seconds)

t = threading.Thread(target=work, args=(2,), name="io-worker-01")

print(f"name:       {t.name}")
print(f"alive?      {t.is_alive()}")    # False - not started
print(f"ident:      {t.ident}")         # None - not started

t.start()
print(f"alive?      {t.is_alive()}")    # True
print(f"ident:      {t.ident}")         # now an integer
print(f"native_id:  {t.native_id}")     # kernel thread id

t.join()
print(f"alive?      {t.is_alive()}")    # False - finished
OUTPUT
name: io-worker-01 alive? False ident: None alive? True ident: 140490980718272 native_id: 1130 alive? False
🔧
Fixed: ident vs native_idसुधार: ident बनाम native_id已修正:ident 与 native_id

The original called ident the "OS thread identifier" and showed a made-up native_id of 1234. ident is Python's own identifier for the thread (on Linux it happens to be a memory address, which is why it is so large). native_id is the ID the operating system uses — the one you would see in tools like top. The values above are from a real run; yours will differ every time.

मूल में ident को "OS thread identifier" कहा गया और 1234 वाला बनावटी native_id दिखाया गया। ident thread के लिए Python का अपना identifier है (Linux पर यह संयोग से memory address होता है, इसलिए इतना बड़ा है)। native_id वह ID है जो operating system इस्तेमाल करता है — जो आप top जैसे tools में देखेंगे। ऊपर की values असली run की हैं; आपकी हर बार अलग होंगी।

原文把 ident 叫作"操作系统线程标识符",并展示了一个虚构的 native_id 值 1234。ident 是 Python 自己给线程的标识符(在 Linux 上它恰好是一个内存地址,所以数字很大)。native_id 才是 操作系统 使用的 ID — 也就是你在 top 等工具中看到的那个。上面的值来自真实运行;你每次运行的结果都会不同。


SectionSection第 06 节

start() vs run() — The Critical Difference ⚠️start() बनाम run() — सबसे ज़रूरी फ़र्क़ ⚠️start() vs run() — 关键区别 ⚠️

This is the most misunderstood thing in Python threading. Both methods exist and both call your target — but only start() creates a new thread. Mixing them up silently kills your concurrency.

Python threading में यही सबसे ज़्यादा ग़लत समझी जाने वाली बात है। दोनों methods हैं और दोनों आपका target बुलाते हैं — पर सिर्फ़ start() नया thread बनाता है। इन्हें मिला देने से आपकी concurrency चुपचाप ख़त्म हो जाती है।

这是 Python 多线程中最常被误解的一点。两个方法都存在,也都会调用你的 target — 但只有 start() 会创建 新线程。把它们搞混会悄悄地扼杀并发。

Figure 2 — What Happens With start() vs run()चित्र 2 — start() बनाम run() में क्या होता है图 2 — start() 与 run() 各自发生了什么
start() vs run()✓ t.start() — correct✓ t.start() — सही✓ t.start() — 正确MAINMAIN主线程NEW THREADNEW THREAD新线程spawnt.start()target()keeps goingworking…t.join()2 threads at once✗ t.run() — wrong✗ t.run() — ग़लत✗ t.run() — 错误MAIN (only)MAIN (अकेला)只有主线程t.run()target() runshere, in mainmain is blockedjust 1 thread — no concurrency

start() asks the OS for a new thread and calls run() inside it. Calling run() yourself is just a normal method call in the current thread.

start() OS से नया thread माँगता है और उसके अंदर run() बुलाता है। ख़ुद run() बुलाना current thread में बस एक सामान्य method call है।

start() 向操作系统申请一个新线程并在其中调用 run()。自己调用 run() 只是在当前线程中的一次普通方法调用。

import threading, time

def slow_task():
    print(f"  [{threading.current_thread().name}] working...")
    time.sleep(2)
    print(f"  [{threading.current_thread().name}] done")

# === CORRECT: use start() ===
print("--- with start() ---")
start_time = time.time()
threads = [threading.Thread(target=slow_task, name=f"t{i}") for i in range(3)]
for t in threads: t.start()
for t in threads: t.join()
print(f"took {time.time() - start_time:.1f}s")

# === WRONG: use run() ===
print("\n--- with run() ---")
start_time = time.time()
threads = [threading.Thread(target=slow_task, name=f"t{i}") for i in range(3)]
for t in threads: t.run()       # NO new threads created!
print(f"took {time.time() - start_time:.1f}s")
OUTPUT
--- with start() --- [t0] working... [t1] working... [t2] working... [t0] done [t2] done [t1] done took 2.0s --- with run() --- [MainThread] working... [MainThread] done [MainThread] working... [MainThread] done [MainThread] working... [MainThread] done took 6.0s
🔧
Fixed: Real Output Is Messierसुधार: असली Output ज़्यादा उलझा होता है已修正:真实输出会更乱

The original showed three tidy "working…" lines. In our real run two of them landed on the same line: print() writes the text and the newline in two steps, and another thread can sneak in between. This is normal for threads printing at the same moment. The timings — 2.0 s with start(), 6.0 s with run() — match.

मूल में तीन साफ़-सुथरी "working…" lines दिखाई गई थीं। हमारे असली run में उनमें से दो एक ही line पर आ गईं: print() text और newline दो क़दमों में लिखता है, और बीच में कोई दूसरा thread घुस सकता है। एक ही पल में print करने वाले threads के लिए यह सामान्य है। Timings — start() से 2.0 s, run() से 6.0 s — मेल खाती हैं।

原文展示了三行整齐的"working…"。在我们的真实运行中,其中两条落在了 同一行:print() 分两步写出文字和换行符,另一个线程可能在中间插进来。多个线程同时打印时这很正常。计时 — 用 start() 2.0 秒,用 run() 6.0 秒 — 与原文一致。

🔀 Simulator: start() vs run()🔀 Simulator: start() बनाम run()🔀 模拟器:start() vs run()

Three threads, each sleeping 0.5 s. See which thread really runs each task, and how long it all takes. The timeline was recorded from a real run.

तीन threads, हर एक 0.5 s sleep करता है। देखिए हर काम असल में कौन-सा thread चलाता है, और सब में कितना समय लगता है। Timeline असली run से रिकॉर्ड की गई है।

三个线程,每个 sleep 0.5 秒。看看每个任务究竟由哪个线程运行,以及总共花了多长时间。时间线来自真实运行的记录。

start() vs run()start() बनाम run()start() vs run()recorded from a real runअसली run से रिकॉर्ड来自真实运行的记录
Call:बुलाएँ:调用:
OutputOutput输出
🔐
The Rule You Must Never Breakवह नियम जो कभी नहीं तोड़ना绝不能打破的规则

Always call t.start(). Never call t.run() yourself. run() is meant to be called by Python inside the new thread. Call it manually and everything runs in the current thread — zero concurrency, exactly like calling the function directly.

हमेशा t.start() बुलाइए। t.run() ख़ुद कभी मत बुलाइए। run() को Python नए thread के अंदर बुलाता है। हाथ से बुलाएँगे तो सब current thread में चलेगा — शून्य concurrency, बिल्कुल function को सीधे बुलाने जैसा।

永远调用 t.start(),永远不要自己调用 t.run()。run() 是给 Python 在新线程里调用的。手动调用它,一切都会在当前线程中运行 — 毫无并发,和直接调用函数完全一样。


SectionSection第 07 节

Passing Arguments — args and kwargs 📦Arguments देना — args और kwargs 📦传递参数 — args 与 kwargs 📦

Pass positional arguments as a tuple to args= and keyword arguments as a dict to kwargs=. Remember the trailing comma for a single argument: args=("only",), not args=("only").

Positional arguments को tuple के रूप में args= में और keyword arguments को dict के रूप में kwargs= में दीजिए। एक argument के लिए आख़िरी comma याद रखिए: args=("only",), args=("only") नहीं।

位置参数以 元组 形式传给 args=,关键字参数以 字典 形式传给 kwargs=。只有一个参数时别忘了末尾的逗号:args=("only",),而不是 args=("only")。

import threading, time

def download(url, timeout, retries=3, verbose=False):
    if verbose:
        print(f"GET {url} (timeout={timeout}s, retries={retries})")
    time.sleep(0.5)
    return f"done: {url}"

# Positional args as a tuple, keyword args as a dict
t = threading.Thread(
    target=download,
    args=("https://api.example.com", 10),        # url, timeout
    kwargs={"retries": 5, "verbose": True}
)
t.start()
t.join()
OUTPUT
GET https://api.example.com (timeout=10s, retries=5)

The trailing-comma bug, run for realआख़िरी comma वाला bug, असल में चलाकर末尾逗号的 bug,真实运行

import threading

def greet(name):
    print(f"Hello, {name}!")

# Bug: ("Mohit") is just a string, not a tuple
t = threading.Thread(target=greet, args=("Mohit"))
t.start()
t.join()
print("main: still running - the error happened inside the thread")

# Fix: the trailing comma makes a 1-tuple
t = threading.Thread(target=greet, args=("Mohit",))
t.start()
t.join()
OUTPUT
Exception in thread Thread-1 (greet): Traceback (most recent call last): File "/usr/lib/python3.13/threading.py", line 1044, in _bootstrap_inner self.run() ~~~~~~~~^^ File "/usr/lib/python3.13/threading.py", line 995, in run self._target(*self._args, **self._kwargs) ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TypeError: greet() takes 1 positional argument but 5 were given main: still running - the error happened inside the thread Hello, Mohit!
🔧
Fixed: Where the TypeError Appearsसुधार: TypeError कहाँ दिखता है已修正:TypeError 出现在哪里

The original comment said args=("hello") gives a TypeError. Two details matter. (1) The error happens inside the new thread, not in your main code: Python prints "Exception in thread …" and the main thread carries on, as the output shows. (2) Python unpacks the string into separate characters, so "Mohit" becomes 5 arguments. A one-letter string like ("a") would give exactly 1 argument and silently work — which hides the bug.

मूल comment में कहा गया कि args=("hello") से TypeError आता है। दो बातें ज़रूरी हैं। (1) Error नए thread के अंदर होता है, आपके main code में नहीं: Python "Exception in thread …" print करता है और main thread चलता रहता है, जैसा output दिखाता है। (2) Python string को अलग-अलग characters में खोल देता है, इसलिए "Mohit" 5 arguments बन जाता है। ("a") जैसी एक अक्षर वाली string ठीक 1 argument देती और चुपचाप चल जाती — जो bug को छिपा देता है।

原注释说 args=("hello") 会产生 TypeError。有两个细节很重要。(1) 错误发生在 新线程内部,而不是你的主代码里:Python 打印"Exception in thread …",主线程照常继续,正如输出所示。(2) Python 会把字符串拆成单个字符,所以 "Mohit" 变成了 5 个参数。像 ("a") 这样只有一个字母的字符串恰好是 1 个参数,会 悄悄地正常运行 — 从而把 bug 隐藏起来。

⚠️
Thread Targets Cannot Return Values (Directly)Thread Targets सीधे Value नहीं लौटा सकते线程的 target 无法(直接)返回值

Thread ignores whatever your target returns. To collect results, store them on the thread object (Section 08) or in a shared container. A higher-level tool, concurrent.futures.ThreadPoolExecutor, returns results for you — it is covered in an upcoming tutorial.

Thread आपके target का लौटाया कुछ भी अनदेखा कर देता है। नतीजे जमा करने के लिए उन्हें thread object पर (Section 08) या साझा container में रखिए। एक ऊँचे स्तर का tool, concurrent.futures.ThreadPoolExecutor, नतीजे आपके लिए लौटाता है — यह एक आने वाले tutorial में है।

Thread 会忽略 target 返回的任何值。要收集结果,可以把它们存在线程对象上(第 08 节)或共享容器里。更高层的工具 concurrent.futures.ThreadPoolExecutor 可以替你返回结果 — 将在 后续教程 中介绍。


SectionSection第 08 节

Subclassing Thread — The Class-Based Way 🏭Thread को Subclass करना — Class वाला तरीक़ा 🏭继承 Thread — 基于类的写法 🏭

When your thread needs its own state, helper methods or a clear identity, subclass Thread and override run(). This is the object-oriented alternative to passing a function.

जब आपके thread को अपनी state, helper methods या साफ़ पहचान चाहिए, तो Thread को subclass कीजिए और run() override कीजिए। Function देने का यह object-oriented विकल्प है।

当线程需要自己的状态、辅助方法或清晰的身份时,就继承 Thread 并重写 run()。这是传入函数之外的面向对象写法。

🏭
Step 1 — subclassStep 1 — subclass第 1 步 — 继承
class W(Thread)
Inherit from threading.Thread. Every instance is now a full thread object.
threading.Thread से inherit कीजिए। अब हर instance पूरा thread object है।
继承 threading.Thread。现在每个实例都是完整的线程对象。
🔧
Step 2 — override run()Step 2 — run() override第 2 步 — 重写 run()
def run(self)
This is the ONE method you override. Whatever is inside run() runs in the new thread when start() is called.
यही वह एक method है जिसे आप override करते हैं। start() बुलाने पर run() के अंदर जो है वह नए thread में चलता है।
这是唯一需要重写的方法。调用 start() 时,run() 里的内容会在新线程中运行。
🚀
Step 3 — call start()Step 3 — start() बुलाएँ第 3 步 — 调用 start()
worker.start()
Never call run() yourself. start() creates the new thread, which then calls run().
run() ख़ुद कभी मत बुलाइए। start() नया thread बनाता है, जो फिर run() बुलाता है।
永远不要自己调用 run()。start() 创建新线程,再由新线程调用 run()。
import threading, time

class Downloader(threading.Thread):
    def __init__(self, url, retries=3):
        super().__init__(name=f"dl-{url[-6:]}")    # call parent init!
        self.url = url
        self.retries = retries
        self.result = None                          # place to store output
        self.error = None

    def run(self):                                  # override run()
        try:
            print(f"[{self.name}] fetching...")
            time.sleep(1)                           # pretend it's a real request
            self.result = f"<html of {self.url}>"
        except Exception as e:
            self.error = e

# Use it exactly like a Thread
urls = ["example.com/a", "example.com/b", "example.com/c"]
workers = [Downloader(u) for u in urls]
for w in workers: w.start()           # start(), never run()
for w in workers: w.join()

for w in workers:
    if w.error:
        print(f"{w.name} FAILED: {w.error}")
    else:
        print(f"{w.name} -> {w.result}")
OUTPUT
[dl-.com/a] fetching... [dl-.com/b] fetching... [dl-.com/c] fetching... dl-.com/a -> <html of example.com/a> dl-.com/b -> <html of example.com/b> dl-.com/c -> <html of example.com/c>
🔧
Fixed: The Thread Names Were Wrongसुधार: Thread के नाम ग़लत थे已修正:线程名称不对

The original output showed names like dl-com/a. But url[-6:] takes the last 6 characters, and "example.com/a"[-6:] is ".com/a" — so the real names are dl-.com/a, as the real output above shows.

मूल output में dl-com/a जैसे नाम थे। पर url[-6:] आख़िरी 6 characters लेता है, और "example.com/a"[-6:] ".com/a" है — इसलिए असली नाम dl-.com/a हैं, जैसा ऊपर का असली output दिखाता है।

原输出显示的名字是 dl-com/a。但 url[-6:] 取的是最后 6 个字符,而 "example.com/a"[-6:] 是 ".com/a" — 所以真实的名字是 dl-.com/a,正如上面的真实输出所示。

🔑
Non-Negotiable — Call super().__init__()ज़रूरी — super().__init__() बुलाइए必须调用 super().__init__()

If you override __init__, call super().__init__() first. Otherwise Thread's own setup (name, ident, the alive flag) never happens, and start() fails. Real run:

अगर आप __init__ override करते हैं, तो पहले super().__init__() बुलाइए। वरना Thread की अपनी तैयारी (name, ident, alive flag) कभी नहीं होती, और start() fail होता है। असली run:

如果重写了 __init__,要先调用 super().__init__()。否则 Thread 自身的初始化(名字、ident、存活标志)不会发生,start() 就会失败。真实运行:

import threading

class Worker(threading.Thread):
    def __init__(self, job):
        self.job = job              # forgot super().__init__()

    def run(self):
        print("working on", self.job)

try:
    Worker("report").start()
except RuntimeError as e:
    print("RuntimeError:", e)
OUTPUT
RuntimeError: thread.__init__() not called

SectionSection第 09 节

Function vs Class — When to Use Which ⚖️Function बनाम Class — कब कौन-सा ⚖️函数 vs 类 — 何时用哪个 ⚖️

Aspectपहलू方面Function-basedFunction-based基于函数Class-based (subclass)Class-based (subclass)基于类(子类)
Boilerplateअतिरिक्त code样板代码Very little — one lineबहुत कम — एक line很少 — 一行A whole classपूरी class一整个类
Storing stateState रखना保存状态Awkward — globals or closuresअसुविधाजनक — globals या closures别扭 — 用全局变量或闭包Natural — self.attrस्वाभाविक — self.attr自然 — self.attr
Returning resultsनतीजे लौटाना返回结果Through a shared containerसाझा container से通过共享容器Store on self.result, read after join()self.result पर रखें, join() के बाद पढ़ें存到 self.result,join() 后读取
Helper methodsHelper methods辅助方法Not built inBuilt-in नहीं不自带Add methods like any classकिसी भी class की तरह methods जोड़ें像普通类一样添加方法
One-off scriptsएक बार की scripts一次性脚本Perfect fitबिल्कुल सही非常合适Overkillज़रूरत से ज़्यादा大材小用
Reusable worker typesदोबारा इस्तेमाल होने वाले workers可复用的工作者类型Feels ad hocजुगाड़ जैसा लगता है显得零散Clean and testableसाफ़ और testable整洁、易测试
📈
Practical Ruleव्यावहारिक नियम实用规则

Start with the function-based way for quick scripts. Move to subclassing Thread when you need lasting state, helper methods or a reusable worker type.

जल्दी की scripts के लिए function वाले तरीक़े से शुरू कीजिए। जब लंबी state, helper methods या दोबारा इस्तेमाल होने वाला worker type चाहिए, तब Thread को subclass कीजिए।

快速脚本先用基于函数的写法。需要持久状态、辅助方法或可复用的工作者类型时,再改为继承 Thread。


SectionSection第 10 节

The Thread Life Cycle 🔄Thread का जीवन-चक्र 🔄线程的生命周期 🔄

Figure 3 — The Life Cycle of a Threadचित्र 3 — Thread का जीवन-चक्र图 3 — 线程的生命周期
Thread life cycle1 · NEW1 · NEW1 · 新建Thread(...)alive: Falsestart()ALIVE — is_alive() is TrueALIVE — is_alive() True है存活 — is_alive() 为 True2 · RUNNABLE2 · RUNNABLE2 · 就绪ready, waiting its turnतैयार, बारी का इंतज़ार就绪,等待轮到它3 · RUNNING3 · RUNNING3 · 运行executing run()run() चला रहा正在执行 run()WAITINGWAITING等待中sleep · I/O · join()run() ends4 · TERMINATED4 · TERMINATED4 · 终止alive: Falsecannot restartफिर शुरू नहीं不能重启t.join() waits until the thread reaches state 4t.join() तब तक इंतज़ार करता है जब तक thread state 4 तक न पहुँचेt.join() 会一直等到线程进入状态 4

One way only: New → (Runnable ⇄ Running, sometimes Waiting) → Terminated. A finished thread object cannot be started again.

सिर्फ़ एक दिशा: New → (Runnable ⇄ Running, कभी-कभी Waiting) → Terminated। ख़त्म हुआ thread object फिर से start नहीं हो सकता।

只能单向前进:新建 → (就绪 ⇄ 运行,有时等待)→ 终止。已结束的线程对象不能再次启动。

🔧
Fixed: The Missing Waiting Stateसुधार: छूटी हुई Waiting State已修正:缺少的"等待"状态

The original showed four states and said a thread cycles between Runnable and Running. It left out the most important case for this tutorial: a thread that calls sleep(), waits for I/O or calls join() is waiting — not runnable at all until the wait ends. That is the moment other threads get to run. During waiting, is_alive() is still True. Python itself only tells you "alive or not"; the other states are managed by the operating system.

मूल में चार states दिखाई गई थीं और कहा गया कि thread Runnable और Running के बीच घूमता है। इसमें इस tutorial का सबसे ज़रूरी case छूट गया: जो thread sleep() बुलाता है, I/O का इंतज़ार करता है या join() बुलाता है, वह waiting में है — इंतज़ार ख़त्म होने तक बिल्कुल runnable नहीं। यही वह पल है जब दूसरे threads चलते हैं। Waiting के दौरान is_alive() फिर भी True है। Python ख़ुद सिर्फ़ "alive है या नहीं" बताता है; बाक़ी states operating system सँभालता है।

原文展示了四种状态,并说线程在就绪和运行之间来回切换。它漏掉了本教程最重要的情况:调用 sleep()、等待 I/O 或调用 join() 的线程处于 等待 状态 — 在等待结束前根本不是就绪状态。正是在这时其他线程才得以运行。等待期间 is_alive() 仍然是 True。Python 本身只告诉你"是否存活";其余状态由操作系统管理。

🔄 Simulator: Lifecycle Lab🔄 Simulator: Lifecycle Lab🔄 模拟器:生命周期实验室

Drive one thread through its life. The worker waits on an Event, so you decide when it finishes. Try join() before start(), or start() twice. Every output was checked against Python on 250 random sequences.

एक thread को उसके जीवन से गुज़ारिए। Worker एक Event का इंतज़ार करता है, इसलिए वह कब ख़त्म हो यह आप तय करते हैं। start() से पहले join(), या दो बार start() आज़माइए। हर output 250 random sequences पर Python से जाँचा गया।

带领一个线程走完它的一生。工作线程在等待一个 Event,所以由 你 决定它何时结束。试试在 start() 之前调用 join(),或者调用两次 start()。每个输出都已在 250 个随机序列上与 Python 核对。

Lifecycle LabLifecycle Lab生命周期实验室250 sequences checked250 sequences जाँचे已核对 250 个序列
NEWcreated, not startedबना, शुरू नहीं已创建,未启动
ALIVErunning or waitingचल रहा या इंतज़ार में运行或等待中
TERMINATEDrun() has endedrun() ख़त्मrun() 已结束
CodeCode代码
OutputOutput输出

SectionSection第 11 节

join() — Waiting for Threads to Finish ⏳join() — Threads के ख़त्म होने का इंतज़ार ⏳join() — 等待线程结束 ⏳

join() is the most important method for controlling threaded programs. It is how one thread (usually main) says: "pause me here until that other thread has finished". Without join(), the main thread can race ahead — even exit — before the workers are done.

Threaded programs को नियंत्रित करने का सबसे ज़रूरी method join() है। इसी से एक thread (आमतौर पर main) कहता है: "मुझे यहाँ रोको जब तक वह दूसरा thread ख़त्म न हो"। join() के बिना main thread workers के ख़त्म होने से पहले आगे भाग सकता है — बाहर भी निकल सकता है।

join() 是控制多线程程序最重要的方法。一个线程(通常是主线程)就是用它说:"在这里暂停我,直到那个线程完成"。没有 join(),主线程可能在工作线程完成之前就跑到前面 — 甚至退出。

Figure 4 — join() Blocks the Caller Until the Target Finishesचित्र 4 — join() Target के ख़त्म होने तक बुलाने वाले को रोकता है图 4 — join() 会阻塞调用者,直到目标线程结束
join blocks the callerMAIN THREADMAIN THREAD主线程WORKER THREADWORKER THREAD工作线程t.start()t.join()working…still working…🔒 BLOCKEDzero CPU,शून्य CPU,不占 CPU,just waitingबस इंतज़ार只是等待done ✓worker ends → main wakes upworker ख़त्म → main जागता है工作线程结束 → 主线程被唤醒continues…

join() puts the calling thread to sleep — using no CPU — until the target thread has terminated. Then the caller wakes up and carries on.

join() बुलाने वाले thread को सुला देता है — बिना CPU के — जब तक target thread terminate न हो। फिर बुलाने वाला जागकर आगे बढ़ता है।

join() 让调用它的线程休眠 — 不占用 CPU — 直到目标线程终止,然后调用者被唤醒继续执行。

Why join() exists — what happens without itjoin() क्यों है — इसके बिना क्या होता हैjoin() 为什么存在 — 没有它会怎样

import threading, time

def worker():
    print("worker: starting")
    time.sleep(2)
    print("worker: finished")

print("main: launching thread")
t = threading.Thread(target=worker)
t.start()
# NO join() - main thread races ahead
print("main: done")
OUTPUT — WITHOUT join()OUTPUT — join() के बिना输出 — 不调用 join()
main: launching thread worker: starting main: done worker: finished

main: done was printed while the worker was still sleeping. In a bigger script, main might use results the worker has not produced yet, or close files it is still writing.

Worker के अभी sleep करते हुए ही main: done print हो गया। बड़ी script में main ऐसे नतीजे इस्तेमाल कर सकता है जो worker ने अभी बनाए ही नहीं, या ऐसी files बंद कर सकता है जिन्हें worker अभी लिख रहा है।

工作线程还在 sleep 时,main: done 就已经打印出来了。在更大的脚本里,主线程可能会使用工作线程还没生成的结果,或关闭它还在写入的文件。

import threading, time

def worker():
    print("worker: starting")
    time.sleep(2)
    print("worker: finished")

print("main: launching thread")
t = threading.Thread(target=worker)
t.start()
t.join()                     # wait here until worker is done
print("main: done")
OUTPUT — WITH join()OUTPUT — join() के साथ输出 — 调用 join()
main: launching thread worker: starting worker: finished main: done
✅
Simple Mental Modelआसान मन का चित्र简单的心智模型

Think of join() as a checkpoint: "I will not go past this line until thread t has finished." The caller stops; the worker keeps going; when the worker exits, the checkpoint opens and the caller continues.

join() को एक checkpoint समझिए: "जब तक thread t ख़त्म न हो, मैं इस line से आगे नहीं जाऊँगा।" बुलाने वाला रुकता है; worker चलता रहता है; worker के बाहर निकलते ही checkpoint खुलता है और बुलाने वाला आगे बढ़ता है।

把 join() 想象成一个检查点:"线程 t 结束之前,我不会越过这一行。"调用者停下;工作线程继续;工作线程一退出,检查点打开,调用者继续执行。

join(timeout=N) — wait only so longjoin(timeout=N) — बस इतना इंतज़ारjoin(timeout=N) — 只等这么久

import threading, time

def very_slow_task():
    time.sleep(10)
    print("task finished after 10s")

t = threading.Thread(target=very_slow_task)
t.start()

print("main: waiting up to 3 seconds...")
t.join(timeout=3)                 # give up after 3s
if t.is_alive():
    print("main: worker still running - moving on without it")
else:
    print("main: worker finished on time")
print("main: continuing with other work")
OUTPUT
main: waiting up to 3 seconds... main: worker still running - moving on without it main: continuing with other work task finished after 10s
⚠️
join(timeout) Does NOT Kill the Threadjoin(timeout) Thread को ख़त्म नहीं करताjoin(timeout) 不会杀死线程

join(timeout=3) does not stop the thread after 3 seconds — it only stops waiting. The thread keeps running (in the real run above, "task finished after 10s" still appeared, and the program waited for it before exiting). Python has no built-in way to kill a thread; you need a cooperative stop signal such as threading.Event (upcoming tutorial).

join(timeout=3) 3 seconds बाद thread को नहीं रोकता — यह सिर्फ़ इंतज़ार रोकता है। Thread चलता रहता है (ऊपर के असली run में "task finished after 10s" फिर भी आया, और program ने बंद होने से पहले उसका इंतज़ार किया)। Python में thread को मारने का कोई built-in तरीक़ा नहीं; threading.Event जैसा सहयोगी stop signal चाहिए (आने वाला tutorial)।

join(timeout=3) 并不会在 3 秒后停止线程 — 它只是停止 等待。线程仍在运行(在上面的真实运行中,"task finished after 10s"依然出现了,程序也等它结束后才退出)。Python 没有内置的强制结束线程的方法;需要像 threading.Event 这样的协作式停止信号(后续教程)。

⏳ Simulator: join(timeout) Lab⏳ Simulator: join(timeout) Lab⏳ 模拟器:join(timeout) 实验室

Pick how long the worker takes and the timeout. See how long main really waits and what is_alive() says afterwards. Checked against 35 real runs (run at 1/5 speed).

चुनिए worker कितना समय लेता है और timeout कितना है। देखिए main असल में कितना इंतज़ार करता है और बाद में is_alive() क्या कहता है। 35 असली runs से जाँचा गया (1/5 गति पर चलाए)।

选择工作线程需要多长时间以及超时时间。看看主线程实际等了多久,之后 is_alive() 返回什么。已与 35 次真实运行核对(以 1/5 速度运行)。

join(timeout) Labjoin(timeout) Labjoin(timeout) 实验室35 real runs35 असली runs35 次真实运行
Worker takes:Worker लेता है:工作线程耗时:
timeout =
is_alive() after joinjoin के बाद is_alive()join 之后的 is_alive()

Joining many threads — the right patternकई threads join करना — सही pattern等待多个线程 — 正确写法

❌ WRONG — one after anotherग़लत — एक के बाद एक错误 — 一个接一个
for t in threads:
    t.start()
    t.join()   # blocks here!
✅ RIGHT — all at onceसही — सब एक साथ正确 — 全部同时
for t in threads: t.start()
for t in threads: t.join()
import threading, time

def task(n):
    time.sleep(2)
    print(f"task {n} done")

threads = [threading.Thread(target=task, args=(i,)) for i in range(5)]

# === WRONG - start & join in the same loop ===
start = time.time()
for t in threads:
    t.start()
    t.join()                  # blocks before the next thread starts
print(f"wrong way: {time.time() - start:.1f}s")

# === RIGHT - separate loops ===
threads = [threading.Thread(target=task, args=(i,)) for i in range(5)]
start = time.time()
for t in threads: t.start()   # fire them all off first
for t in threads: t.join()    # then wait for all
print(f"right way: {time.time() - start:.1f}s")
OUTPUT
task 0 done task 1 done task 2 done task 3 done task 4 done wrong way: 10.0s task 0 done task 1 done task 2 done task 3 done task 4 done right way: 2.0s

🔁 Simulator: Loop Patterns🔁 Simulator: Loop Patterns🔁 模拟器:循环写法

Choose how many threads (each sleeps 0.4 s) and which loop pattern. The bars are real start and end times.

कितने threads (हर एक 0.4 s sleep करता है) और कौन-सा loop pattern, यह चुनिए। Bars असली शुरू और ख़त्म होने के समय हैं।

选择线程数量(每个 sleep 0.4 秒)和循环写法。条形图是真实的开始和结束时间。

Loop PatternsLoop Patterns循环写法real timingsअसली timings真实计时
Threads:Threads:线程数:4
Total timeकुल समय总耗时

Collecting results after join()join() के बाद नतीजे जमा करना在 join() 之后收集结果

import threading, time

class SumWorker(threading.Thread):
    def __init__(self, numbers):
        super().__init__()
        self.numbers = numbers
        self.result = None

    def run(self):
        time.sleep(1)                     # simulate work
        self.result = sum(self.numbers)

# Split the work across 3 workers
w1 = SumWorker([1, 2, 3, 4, 5])
w2 = SumWorker([6, 7, 8, 9, 10])
w3 = SumWorker([11, 12, 13, 14, 15])

for w in (w1, w2, w3): w.start()
for w in (w1, w2, w3): w.join()     # AFTER this line, results are ready

total = w1.result + w2.result + w3.result
print(f"partials: {w1.result}, {w2.result}, {w3.result}")
print(f"total:    {total}")
OUTPUT
partials: 15, 40, 65 total: 120
🔑
Safe to Read After join()join() के बाद पढ़ना सुरक्षितjoin() 之后读取是安全的

When t.join() returns, everything thread t wrote (like self.result) is visible to the joining thread. You do not need a lock just to read the results after a successful join().

जब t.join() लौटता है, तो thread t ने जो कुछ लिखा (जैसे self.result) वह join करने वाले thread को दिखता है। सफल join() के बाद सिर्फ़ नतीजे पढ़ने के लिए lock नहीं चाहिए।

当 t.join() 返回时,线程 t 写入的所有内容(比如 self.result)对调用 join 的线程都是可见的。成功 join() 之后,仅仅读取结果不需要加锁。

A thread can be started only onceThread सिर्फ़ एक बार start हो सकता है线程只能启动一次

import threading, time

t = threading.Thread(target=lambda: time.sleep(1))
t.start()
t.join()                               # wait for it to finish
print(f"alive after join?  {t.is_alive()}")    # False

try:
    t.start()                          # try to reuse
except RuntimeError as e:
    print(f"error: {e}")

# You can, however, call join() as many times as you like
t.join()                               # safe - returns immediately
t.join()                               # also safe
print("multiple joins on a finished thread: OK")
OUTPUT
alive after join? False error: threads can only be started once multiple joins on a finished thread: OK

Never join() a thread from itselfThread को उसी के अंदर से join() मत कीजिए不要在线程内部 join() 自己

A thread joining itself would wait for itself for ever. Python catches this and raises RuntimeError:

ख़ुद को join करने वाला thread अपना ही इंतज़ार हमेशा करता रहता। Python इसे पकड़कर RuntimeError raise करता है:

一个线程 join 自己,就会永远等待自己。Python 会捕获这种情况并抛出 RuntimeError:

import threading

def bad_worker():
    # current_thread() returns the Thread object we're inside
    me = threading.current_thread()
    try:
        me.join()                      # waiting for myself - nonsense!
    except RuntimeError as e:
        print(f"caught: {e}")

t = threading.Thread(target=bad_worker)
t.start()
t.join()
OUTPUT
caught: cannot join current thread

join() returns Nonejoin() None लौटाता हैjoin() 返回 None

import threading

def compute():
    return 42                          # this return value is DROPPED

t = threading.Thread(target=compute)
t.start()
result = t.join()                      # join returns None, not 42
print(f"join returned: {result}")      # None
print("to capture the return value, use a subclass or a shared container")
OUTPUT
join returned: None to capture the return value, use a subclass or a shared container

Practical example — parallel file processing with progressकाम का example — progress के साथ parallel file processing实用示例 — 带进度显示的并行文件处理

import threading, time, random

random.seed(7)                          # same sizes on every run

class FileProcessor(threading.Thread):
    def __init__(self, filename):
        super().__init__(name=f"proc-{filename}")
        self.filename = filename
        self.bytes_read = 0
        self.size = random.randint(1, 4)    # pretend file size in MB

    def run(self):
        time.sleep(self.size)               # pretend it takes 1 s per MB
        self.bytes_read = self.size * 1024 * 1024

# Kick off 5 workers
files = ["log01.txt", "log02.txt", "log03.txt", "log04.txt", "log05.txt"]
workers = [FileProcessor(f) for f in files]
start = time.time()
for w in workers: w.start()

# Poll with short join timeouts to show live progress
while any(w.is_alive() for w in workers):
    for w in workers:
        w.join(timeout=0.5)                 # wait up to 0.5 s per worker
    still_running = sum(1 for w in workers if w.is_alive())
    print(f"[{time.time() - start:3.1f}s] {len(workers) - still_running}/{len(workers)} finished")

# All done - safe to read results
print("\n=== Summary ===")
for w in workers:
    print(f"{w.filename}: {w.bytes_read / 1024 / 1024:.1f} MB")
OUTPUT
[1.5s] 2/5 finished [2.5s] 3/5 finished [3.5s] 4/5 finished [4.0s] 5/5 finished === Summary === log01.txt: 3.0 MB log02.txt: 2.0 MB log03.txt: 4.0 MB log04.txt: 1.0 MB log05.txt: 1.0 MB
🔧
Fixed: Seeded Sizes and a Real Progress Logसुधार: तय Sizes और असली Progress Log已修正:固定的大小与真实的进度日志

The original used random sizes without a seed and showed an invented progress log. We added random.seed(7) and a time stamp, and the output above is real. Notice that the progress lines are not evenly spaced and "4/5" appears twice: each pass of the while loop can wait up to 0.5 s per still-running worker, so one pass can take up to 2.5 s. We also removed the unused done attribute.

मूल में बिना seed के random sizes थे और एक बनावटी progress log दिखाया गया था। हमने random.seed(7) और time stamp जोड़ा, और ऊपर का output असली है। ध्यान दीजिए, progress lines बराबर दूरी पर नहीं हैं और "4/5" दो बार आता है: while loop का हर चक्कर हर चल रहे worker पर 0.5 s तक इंतज़ार कर सकता है, इसलिए एक चक्कर में 2.5 s तक लग सकते हैं। हमने इस्तेमाल न होने वाला done attribute भी हटाया।

原代码使用了没有种子的随机大小,并展示了一份编造的进度日志。我们加上了 random.seed(7) 和时间戳,上面的输出是真实的。注意进度行的间隔并不均匀,"4/5"还出现了两次:while 循环每一轮对 每个仍在运行的工作线程 最多等 0.5 秒,所以一轮最多可能花 2.5 秒。我们还删除了没被使用的 done 属性。

📈
Short-Timeout join() as a Poll Loopछोटे Timeout वाला join() Poll Loop की तरह用短超时的 join() 做轮询循环

Calling join(timeout=0.5) in a loop is a neat way to print progress or heartbeats while background work runs. It is better than checking is_alive() in a tight loop, because the caller really sleeps instead of spinning the CPU.

Loop में join(timeout=0.5) बुलाना background काम चलते समय progress या heartbeat print करने का अच्छा तरीक़ा है। यह tight loop में is_alive() जाँचने से बेहतर है, क्योंकि बुलाने वाला सच में सोता है, CPU नहीं घुमाता।

在循环中调用 join(timeout=0.5),是在后台任务运行时打印进度或心跳的简洁方法。它比在紧凑循环里反复检查 is_alive() 更好,因为调用者是真正在休眠,而不是空转 CPU。

join() cheat sheetjoin() cheat sheetjoin() 速查表

CallCall调用Behaviourव्यवहार行为Returnsलौटाता है返回值
t.join()Waits until t finishest के ख़त्म होने तक इंतज़ार一直等到 t 结束None
t.join(timeout=5)Waits at most 5 secondsज़्यादा से ज़्यादा 5 seconds इंतज़ार最多等 5 秒None (then check is_alive())None (फिर is_alive() जाँचें)None(之后检查 is_alive())
t.join(0)Returns at once — like a status checkतुरंत लौटता है — status check जैसा立即返回 — 相当于检查状态None
t.join() before t.start()t.start() से पहले t.join()在 t.start() 之前调用 t.join()Raises RuntimeErrorRuntimeError raise抛出 RuntimeError—
t.join() after t finishedt ख़त्म होने के बाद t.join()t 结束后调用 t.join()Returns at once (safe to repeat)तुरंत लौटता है (दोहराना सुरक्षित)立即返回(可安全重复调用)None
t.join() inside t itselft के अंदर t.join()在 t 内部调用 t.join()Raises RuntimeError — deadlock guardRuntimeError raise — deadlock से बचाव抛出 RuntimeError — 防止死锁—

SectionSection第 12 节

Bug Doctor, Golden Rules and Quiz 🏆Bug Doctor, सुनहरे नियम और Quiz 🏆Bug 医生、黄金法则与小测验 🏆

🩺 Simulator: Bug Doctor🩺 Simulator: Bug Doctor🩺 模拟器:Bug 医生

Six real threading mistakes from this tutorial. Buggy code on the left, fixed code on the right, changed lines marked. Every output is from a real run.

इस tutorial की छह असली threading ग़लतियाँ। बाईं ओर buggy code, दाईं ओर ठीक किया हुआ, बदली lines चिह्नित। हर output असली run से है।

本教程中的六个真实多线程错误。左边是有 bug 的代码,右边是修正后的代码,改动的行已标出。每个输出都来自真实运行。

Bug DoctorBug DoctorBug 医生6 bugs · real outputs6 bugs · असली outputs6 个 bug · 真实输出
❌ BuggyBuggy有 bug
✅ Fixedठीक किया已修正
🌲 Threading Basics — Rules to RememberThreading की बुनियाद — याद रखने वाले नियम多线程基础 — 必须记住的规则
1
Always join() your worker threads before you use their results or let the program finish. Without it, main can race ahead and read results that do not exist yet.
अपने worker threads के नतीजे इस्तेमाल करने या program ख़त्म होने देने से पहले हमेशा उन्हें join() कीजिए। इसके बिना main आगे भागकर ऐसे नतीजे पढ़ सकता है जो अभी हैं ही नहीं।
在使用工作线程的结果或让程序结束之前,一定要 join() 它们。否则主线程可能抢先读取还不存在的结果。
2
When starting many threads, use two loops: one to start() them all, then one to join() them all. Putting both in one loop runs the threads one after another.
कई threads शुरू करते समय दो loops लीजिए: एक सबको start() करने के लिए, फिर एक सबको join() करने के लिए। दोनों एक loop में रखने से threads एक के बाद एक चलते हैं।
启动多个线程时用 两个循环:一个循环全部 start(),再一个循环全部 join()。把两者放进同一个循环会让线程一个接一个地运行。
3
join(timeout=N) does not stop the thread — it only stops waiting. Check t.is_alive() afterwards to know which one happened.
join(timeout=N) thread को नहीं रोकता — सिर्फ़ इंतज़ार रोकता है। बाद में t.is_alive() जाँचकर जानिए कि क्या हुआ।
join(timeout=N) 不会停止线程 — 它只是停止等待。之后检查 t.is_alive() 才知道发生了什么。
4
join() always returns None. To get a result, store it on self.result in a Thread subclass (or in a shared container) and read it after join().
join() हमेशा None लौटाता है। नतीजा पाने के लिए उसे Thread subclass में self.result पर (या साझा container में) रखिए और join() के बाद पढ़िए।
join() 永远返回 None。要获得结果,就在 Thread 子类中把它存到 self.result(或共享容器)里,并在 join() 之后 读取。
5
Always call start(), never run(). run() executes in the current thread and gives you no concurrency at all.
हमेशा start() बुलाइए, run() कभी नहीं। run() current thread में चलता है और कोई concurrency नहीं देता।
永远调用 start(),不要调用 run()。run() 在当前线程执行,完全没有并发。
6
A single argument needs a trailing comma: args=("Mohit",). Without it, the string is split into characters — and the error appears inside the thread, not in main.
एक argument के लिए आख़िरी comma चाहिए: args=("Mohit",)। इसके बिना string characters में टूट जाती है — और error thread के अंदर दिखता है, main में नहीं।
只有一个参数时需要末尾的逗号:args=("Mohit",)。否则字符串会被拆成单个字符 — 而且错误出现在线程内部,而不是主线程。
7
In a Thread subclass, call super().__init__() first in your __init__, and put the work in run().
Thread subclass में अपने __init__ में पहले super().__init__() बुलाइए, और काम run() में रखिए।
在 Thread 子类的 __init__ 中先调用 super().__init__(),并把工作放在 run() 里。
8
A thread object can be started only once, and a thread must never join() itself. Calling join() again on a finished thread is safe; calling start() again is not.
एक thread object सिर्फ़ एक बार start हो सकता है, और thread को कभी ख़ुद को join() नहीं करना चाहिए। ख़त्म हुए thread पर फिर से join() सुरक्षित है; फिर से start() नहीं।
一个线程对象只能启动一次,线程也绝不能 join() 自己。对已结束的线程再次调用 join() 是安全的;再次调用 start() 则不行。

Quick Quiz — 8 Questionsछोटा Quiz — 8 सवाल小测验 — 8 道题

🏆 QuizPick an answer to see why.जवाब चुनें और कारण देखें।选一个答案,看看原因。
🎯
Key Takeawayमुख्य सीख关键要点

Threads let one program use waiting time: while one thread waits on I/O, another runs — big wins for I/O-bound work, almost none for pure math. Create a thread with Thread(target=..., args=(...,)) or a subclass that overrides run(), start it with start() (never run()), and wait for it with join() — start all first, join all after, and read results only once join() has returned.

Threads एक program को इंतज़ार का समय इस्तेमाल करने देते हैं: जब एक thread I/O का इंतज़ार करता है, दूसरा चलता है — I/O-bound काम में बड़ा फ़ायदा, सिर्फ़ गणित में लगभग कुछ नहीं। Thread(target=..., args=(...,)) या run() override करने वाली subclass से thread बनाइए, start() से शुरू कीजिए (run() से कभी नहीं), और join() से इंतज़ार कीजिए — पहले सब start, फिर सब join, और नतीजे join() लौटने के बाद ही पढ़िए।

线程让程序能利用等待时间:一个线程等待 I/O 时,另一个线程运行 — 对 I/O 密集型任务收益巨大,对纯数学计算几乎没有。用 Thread(target=..., args=(...,)) 或重写 run() 的子类创建线程,用 start() 启动(绝不用 run()),用 join() 等待 — 先全部启动,再全部等待,并且只在 join() 返回后才读取结果。

🐍 Try Python Yourself

Write, run and visualize Python step by step. No install needed.