[{"data":1,"prerenderedAt":3949},["ShallowReactive",2],{"page-\u002Fpython\u002F21-concurrency-threading-and-multiprocessing":3},{"id":4,"title":5,"body":6,"description":27,"extension":3943,"meta":3944,"navigation":51,"path":3945,"seo":3946,"stem":3947,"__hash__":3948},"content\u002Fpython\u002F21-concurrency-threading-and-multiprocessing.md","21 — Concurrency: Threading & Multiprocessing",{"type":7,"value":8,"toc":3920},"minimark",[9,13,18,913,1312,1319,1464,1481,1637,1654,1668,1773,1904,1928,1935,2113,2128,2135,2342,2364,2371,2515,2529,2719,2741,2750,2835,2857,2942,2949,3141,3159,3163,3239,3243,3319,3323,3400,3404,3407,3580,3853,3857,3916],[10,11,5],"h1",{"id":12},"_21-concurrency-threading-multiprocessing",[14,15,17],"h2",{"id":16},"the-gil-in-practice-when-threading-helps-and-when-it-doesnt","The GIL in Practice — When Threading Helps and When It Doesn't",[19,20,22],"code-wrapper",{"language":21},"python",[23,24,28],"pre",{"className":25,"code":26,"language":21,"meta":27,"style":27},"language-python shiki shiki-themes github-light github-dark","# ── Production: benchmarking I\u002FO-bound vs CPU-bound across threading\u002Fmultiprocessing ──\n# Demonstrates the GIL's impact with real timing, not theoretical claims.\n\nimport threading\nimport multiprocessing as mp\nimport time\nimport requests\nfrom concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor, as_completed\n\n# ── I\u002FO-bound: threading WINS (GIL released during network waits) ──\ndef fetch_url(url: str) -> tuple[str, int]:\n    \"\"\"I\u002FO-bound task — releases GIL during socket recv, allowing true concurrency.\"\"\"\n    try:\n        resp = requests.get(url, timeout=5)\n        return url, resp.status_code\n    except Exception as e:\n        return url, -1\n\ndef io_bound_benchmark(urls: list[str]):\n    # Sequential — one at a time, total = sum of all latencies\n    t0 = time.perf_counter()\n    for url in urls:\n        fetch_url(url)\n    sequential = time.perf_counter() - t0\n\n    # Threaded — overlapping I\u002FO waits, total ≈ slowest single request\n    t0 = time.perf_counter()\n    with ThreadPoolExecutor(max_workers=10) as pool:\n        list(pool.map(fetch_url, urls))\n    threaded = time.perf_counter() - t0\n\n    print(f\"I\u002FO-bound: sequential={sequential:.2f}s, threaded={threaded:.2f}s\")\n    print(f\"  speedup: {sequential\u002Fthreaded:.1f}x — threading helps (GIL released during I\u002FO)\")\n\n# ── CPU-bound: multiprocessing WINS (GIL blocks true parallelism in threads) ──\ndef cpu_intensive(n: int) -> int:\n    \"\"\"Pure Python computation — GIL prevents parallel bytecode execution across threads.\"\"\"\n    total = 0\n    for i in range(n):\n        total += i * i\n    return total\n\ndef cpu_bound_benchmark(n: int, workers: int):\n    tasks = [n] * workers\n\n    # Sequential\n    t0 = time.perf_counter()\n    for task in tasks:\n        cpu_intensive(task)\n    sequential = time.perf_counter() - t0\n\n    # Threaded — NO speedup (GIL serializes bytecode execution)\n    t0 = time.perf_counter()\n    with ThreadPoolExecutor(max_workers=workers) as pool:\n        list(pool.map(cpu_intensive, tasks))\n    threaded = time.perf_counter() - t0\n\n    # Multiprocessing — TRUE parallelism (separate interpreters, separate GILs)\n    t0 = time.perf_counter()\n    with ProcessPoolExecutor(max_workers=workers) as pool:\n        list(pool.map(cpu_intensive, tasks))\n    multiprocess = time.perf_counter() - t0\n\n    print(f\"CPU-bound: sequential={sequential:.2f}s, threaded={threaded:.2f}s, multiprocess={multiprocess:.2f}s\")\n    print(f\"  threading speedup: {sequential\u002Fthreaded:.1f}x (≈1.0 — GIL blocks parallelism)\")\n    print(f\"  multiprocessing speedup: {sequential\u002Fmultiprocess:.1f}x (≈workers — true parallelism)\")\n\nif __name__ == \"__main__\":   # REQUIRED for ProcessPoolExecutor (spawn re-imports module)\n    cpu_bound_benchmark(n=5_000_000, workers=4)\n","",[29,30,31,40,46,53,64,78,86,94,108,113,119,150,157,166,190,199,214,228,233,249,255,266,281,287,303,308,314,323,348,357,371,376,420,451,456,462,482,488,499,515,532,541,546,566,582,587,593,602,615,621,634,639,645,654,672,680,693,698,704,713,731,738,752,757,803,832,861,866,887],"code",{"__ignoreMap":27},[32,33,36],"span",{"class":34,"line":35},"line",1,[32,37,39],{"class":38},"sdCPZ","# ── Production: benchmarking I\u002FO-bound vs CPU-bound across threading\u002Fmultiprocessing ──\n",[32,41,43],{"class":34,"line":42},2,[32,44,45],{"class":38},"# Demonstrates the GIL's impact with real timing, not theoretical claims.\n",[32,47,49],{"class":34,"line":48},3,[32,50,52],{"emptyLinePlaceholder":51},true,"\n",[32,54,56,60],{"class":34,"line":55},4,[32,57,59],{"class":58},"svdQ7","import",[32,61,63],{"class":62},"ssxIu"," threading\n",[32,65,67,69,72,75],{"class":34,"line":66},5,[32,68,59],{"class":58},[32,70,71],{"class":62}," multiprocessing ",[32,73,74],{"class":58},"as",[32,76,77],{"class":62}," mp\n",[32,79,81,83],{"class":34,"line":80},6,[32,82,59],{"class":58},[32,84,85],{"class":62}," time\n",[32,87,89,91],{"class":34,"line":88},7,[32,90,59],{"class":58},[32,92,93],{"class":62}," requests\n",[32,95,97,100,103,105],{"class":34,"line":96},8,[32,98,99],{"class":58},"from",[32,101,102],{"class":62}," concurrent.futures ",[32,104,59],{"class":58},[32,106,107],{"class":62}," ThreadPoolExecutor, ProcessPoolExecutor, as_completed\n",[32,109,111],{"class":34,"line":110},9,[32,112,52],{"emptyLinePlaceholder":51},[32,114,116],{"class":34,"line":115},10,[32,117,118],{"class":38},"# ── I\u002FO-bound: threading WINS (GIL released during network waits) ──\n",[32,120,122,125,129,132,136,139,141,144,147],{"class":34,"line":121},11,[32,123,124],{"class":58},"def",[32,126,128],{"class":127},"sIsaT"," fetch_url",[32,130,131],{"class":62},"(url: ",[32,133,135],{"class":134},"snvgF","str",[32,137,138],{"class":62},") -> tuple[",[32,140,135],{"class":134},[32,142,143],{"class":62},", ",[32,145,146],{"class":134},"int",[32,148,149],{"class":62},"]:\n",[32,151,153],{"class":34,"line":152},12,[32,154,156],{"class":155},"sJ6F3","    \"\"\"I\u002FO-bound task — releases GIL during socket recv, allowing true concurrency.\"\"\"\n",[32,158,160,163],{"class":34,"line":159},13,[32,161,162],{"class":58},"    try",[32,164,165],{"class":62},":\n",[32,167,169,172,175,178,182,184,187],{"class":34,"line":168},14,[32,170,171],{"class":62},"        resp ",[32,173,174],{"class":58},"=",[32,176,177],{"class":62}," requests.get(url, ",[32,179,181],{"class":180},"sCrzJ","timeout",[32,183,174],{"class":58},[32,185,186],{"class":134},"5",[32,188,189],{"class":62},")\n",[32,191,193,196],{"class":34,"line":192},15,[32,194,195],{"class":58},"        return",[32,197,198],{"class":62}," url, resp.status_code\n",[32,200,202,205,208,211],{"class":34,"line":201},16,[32,203,204],{"class":58},"    except",[32,206,207],{"class":134}," Exception",[32,209,210],{"class":58}," as",[32,212,213],{"class":62}," e:\n",[32,215,217,219,222,225],{"class":34,"line":216},17,[32,218,195],{"class":58},[32,220,221],{"class":62}," url, ",[32,223,224],{"class":58},"-",[32,226,227],{"class":134},"1\n",[32,229,231],{"class":34,"line":230},18,[32,232,52],{"emptyLinePlaceholder":51},[32,234,236,238,241,244,246],{"class":34,"line":235},19,[32,237,124],{"class":58},[32,239,240],{"class":127}," io_bound_benchmark",[32,242,243],{"class":62},"(urls: list[",[32,245,135],{"class":134},[32,247,248],{"class":62},"]):\n",[32,250,252],{"class":34,"line":251},20,[32,253,254],{"class":38},"    # Sequential — one at a time, total = sum of all latencies\n",[32,256,258,261,263],{"class":34,"line":257},21,[32,259,260],{"class":62},"    t0 ",[32,262,174],{"class":58},[32,264,265],{"class":62}," time.perf_counter()\n",[32,267,269,272,275,278],{"class":34,"line":268},22,[32,270,271],{"class":58},"    for",[32,273,274],{"class":62}," url ",[32,276,277],{"class":58},"in",[32,279,280],{"class":62}," urls:\n",[32,282,284],{"class":34,"line":283},23,[32,285,286],{"class":62},"        fetch_url(url)\n",[32,288,290,293,295,298,300],{"class":34,"line":289},24,[32,291,292],{"class":62},"    sequential ",[32,294,174],{"class":58},[32,296,297],{"class":62}," time.perf_counter() ",[32,299,224],{"class":58},[32,301,302],{"class":62}," t0\n",[32,304,306],{"class":34,"line":305},25,[32,307,52],{"emptyLinePlaceholder":51},[32,309,311],{"class":34,"line":310},26,[32,312,313],{"class":38},"    # Threaded — overlapping I\u002FO waits, total ≈ slowest single request\n",[32,315,317,319,321],{"class":34,"line":316},27,[32,318,260],{"class":62},[32,320,174],{"class":58},[32,322,265],{"class":62},[32,324,326,329,332,335,337,340,343,345],{"class":34,"line":325},28,[32,327,328],{"class":58},"    with",[32,330,331],{"class":62}," ThreadPoolExecutor(",[32,333,334],{"class":180},"max_workers",[32,336,174],{"class":58},[32,338,339],{"class":134},"10",[32,341,342],{"class":62},") ",[32,344,74],{"class":58},[32,346,347],{"class":62}," pool:\n",[32,349,351,354],{"class":34,"line":350},29,[32,352,353],{"class":134},"        list",[32,355,356],{"class":62},"(pool.map(fetch_url, urls))\n",[32,358,360,363,365,367,369],{"class":34,"line":359},30,[32,361,362],{"class":62},"    threaded ",[32,364,174],{"class":58},[32,366,297],{"class":62},[32,368,224],{"class":58},[32,370,302],{"class":62},[32,372,374],{"class":34,"line":373},31,[32,375,52],{"emptyLinePlaceholder":51},[32,377,379,382,385,388,391,394,397,400,403,406,408,411,413,415,418],{"class":34,"line":378},32,[32,380,381],{"class":134},"    print",[32,383,384],{"class":62},"(",[32,386,387],{"class":58},"f",[32,389,390],{"class":155},"\"I\u002FO-bound: sequential=",[32,392,393],{"class":134},"{",[32,395,396],{"class":62},"sequential",[32,398,399],{"class":58},":.2f",[32,401,402],{"class":134},"}",[32,404,405],{"class":155},"s, threaded=",[32,407,393],{"class":134},[32,409,410],{"class":62},"threaded",[32,412,399],{"class":58},[32,414,402],{"class":134},[32,416,417],{"class":155},"s\"",[32,419,189],{"class":62},[32,421,423,425,427,429,432,434,436,439,441,444,446,449],{"class":34,"line":422},33,[32,424,381],{"class":134},[32,426,384],{"class":62},[32,428,387],{"class":58},[32,430,431],{"class":155},"\"  speedup: ",[32,433,393],{"class":134},[32,435,396],{"class":62},[32,437,438],{"class":58},"\u002F",[32,440,410],{"class":62},[32,442,443],{"class":58},":.1f",[32,445,402],{"class":134},[32,447,448],{"class":155},"x — threading helps (GIL released during I\u002FO)\"",[32,450,189],{"class":62},[32,452,454],{"class":34,"line":453},34,[32,455,52],{"emptyLinePlaceholder":51},[32,457,459],{"class":34,"line":458},35,[32,460,461],{"class":38},"# ── CPU-bound: multiprocessing WINS (GIL blocks true parallelism in threads) ──\n",[32,463,465,467,470,473,475,478,480],{"class":34,"line":464},36,[32,466,124],{"class":58},[32,468,469],{"class":127}," cpu_intensive",[32,471,472],{"class":62},"(n: ",[32,474,146],{"class":134},[32,476,477],{"class":62},") -> ",[32,479,146],{"class":134},[32,481,165],{"class":62},[32,483,485],{"class":34,"line":484},37,[32,486,487],{"class":155},"    \"\"\"Pure Python computation — GIL prevents parallel bytecode execution across threads.\"\"\"\n",[32,489,491,494,496],{"class":34,"line":490},38,[32,492,493],{"class":62},"    total ",[32,495,174],{"class":58},[32,497,498],{"class":134}," 0\n",[32,500,502,504,507,509,512],{"class":34,"line":501},39,[32,503,271],{"class":58},[32,505,506],{"class":62}," i ",[32,508,277],{"class":58},[32,510,511],{"class":134}," range",[32,513,514],{"class":62},"(n):\n",[32,516,518,521,524,526,529],{"class":34,"line":517},40,[32,519,520],{"class":62},"        total ",[32,522,523],{"class":58},"+=",[32,525,506],{"class":62},[32,527,528],{"class":58},"*",[32,530,531],{"class":62}," i\n",[32,533,535,538],{"class":34,"line":534},41,[32,536,537],{"class":58},"    return",[32,539,540],{"class":62}," total\n",[32,542,544],{"class":34,"line":543},42,[32,545,52],{"emptyLinePlaceholder":51},[32,547,549,551,554,556,558,561,563],{"class":34,"line":548},43,[32,550,124],{"class":58},[32,552,553],{"class":127}," cpu_bound_benchmark",[32,555,472],{"class":62},[32,557,146],{"class":134},[32,559,560],{"class":62},", workers: ",[32,562,146],{"class":134},[32,564,565],{"class":62},"):\n",[32,567,569,572,574,577,579],{"class":34,"line":568},44,[32,570,571],{"class":62},"    tasks ",[32,573,174],{"class":58},[32,575,576],{"class":62}," [n] ",[32,578,528],{"class":58},[32,580,581],{"class":62}," workers\n",[32,583,585],{"class":34,"line":584},45,[32,586,52],{"emptyLinePlaceholder":51},[32,588,590],{"class":34,"line":589},46,[32,591,592],{"class":38},"    # Sequential\n",[32,594,596,598,600],{"class":34,"line":595},47,[32,597,260],{"class":62},[32,599,174],{"class":58},[32,601,265],{"class":62},[32,603,605,607,610,612],{"class":34,"line":604},48,[32,606,271],{"class":58},[32,608,609],{"class":62}," task ",[32,611,277],{"class":58},[32,613,614],{"class":62}," tasks:\n",[32,616,618],{"class":34,"line":617},49,[32,619,620],{"class":62},"        cpu_intensive(task)\n",[32,622,624,626,628,630,632],{"class":34,"line":623},50,[32,625,292],{"class":62},[32,627,174],{"class":58},[32,629,297],{"class":62},[32,631,224],{"class":58},[32,633,302],{"class":62},[32,635,637],{"class":34,"line":636},51,[32,638,52],{"emptyLinePlaceholder":51},[32,640,642],{"class":34,"line":641},52,[32,643,644],{"class":38},"    # Threaded — NO speedup (GIL serializes bytecode execution)\n",[32,646,648,650,652],{"class":34,"line":647},53,[32,649,260],{"class":62},[32,651,174],{"class":58},[32,653,265],{"class":62},[32,655,657,659,661,663,665,668,670],{"class":34,"line":656},54,[32,658,328],{"class":58},[32,660,331],{"class":62},[32,662,334],{"class":180},[32,664,174],{"class":58},[32,666,667],{"class":62},"workers) ",[32,669,74],{"class":58},[32,671,347],{"class":62},[32,673,675,677],{"class":34,"line":674},55,[32,676,353],{"class":134},[32,678,679],{"class":62},"(pool.map(cpu_intensive, tasks))\n",[32,681,683,685,687,689,691],{"class":34,"line":682},56,[32,684,362],{"class":62},[32,686,174],{"class":58},[32,688,297],{"class":62},[32,690,224],{"class":58},[32,692,302],{"class":62},[32,694,696],{"class":34,"line":695},57,[32,697,52],{"emptyLinePlaceholder":51},[32,699,701],{"class":34,"line":700},58,[32,702,703],{"class":38},"    # Multiprocessing — TRUE parallelism (separate interpreters, separate GILs)\n",[32,705,707,709,711],{"class":34,"line":706},59,[32,708,260],{"class":62},[32,710,174],{"class":58},[32,712,265],{"class":62},[32,714,716,718,721,723,725,727,729],{"class":34,"line":715},60,[32,717,328],{"class":58},[32,719,720],{"class":62}," ProcessPoolExecutor(",[32,722,334],{"class":180},[32,724,174],{"class":58},[32,726,667],{"class":62},[32,728,74],{"class":58},[32,730,347],{"class":62},[32,732,734,736],{"class":34,"line":733},61,[32,735,353],{"class":134},[32,737,679],{"class":62},[32,739,741,744,746,748,750],{"class":34,"line":740},62,[32,742,743],{"class":62},"    multiprocess ",[32,745,174],{"class":58},[32,747,297],{"class":62},[32,749,224],{"class":58},[32,751,302],{"class":62},[32,753,755],{"class":34,"line":754},63,[32,756,52],{"emptyLinePlaceholder":51},[32,758,760,762,764,766,769,771,773,775,777,779,781,783,785,787,790,792,795,797,799,801],{"class":34,"line":759},64,[32,761,381],{"class":134},[32,763,384],{"class":62},[32,765,387],{"class":58},[32,767,768],{"class":155},"\"CPU-bound: sequential=",[32,770,393],{"class":134},[32,772,396],{"class":62},[32,774,399],{"class":58},[32,776,402],{"class":134},[32,778,405],{"class":155},[32,780,393],{"class":134},[32,782,410],{"class":62},[32,784,399],{"class":58},[32,786,402],{"class":134},[32,788,789],{"class":155},"s, multiprocess=",[32,791,393],{"class":134},[32,793,794],{"class":62},"multiprocess",[32,796,399],{"class":58},[32,798,402],{"class":134},[32,800,417],{"class":155},[32,802,189],{"class":62},[32,804,806,808,810,812,815,817,819,821,823,825,827,830],{"class":34,"line":805},65,[32,807,381],{"class":134},[32,809,384],{"class":62},[32,811,387],{"class":58},[32,813,814],{"class":155},"\"  threading speedup: ",[32,816,393],{"class":134},[32,818,396],{"class":62},[32,820,438],{"class":58},[32,822,410],{"class":62},[32,824,443],{"class":58},[32,826,402],{"class":134},[32,828,829],{"class":155},"x (≈1.0 — GIL blocks parallelism)\"",[32,831,189],{"class":62},[32,833,835,837,839,841,844,846,848,850,852,854,856,859],{"class":34,"line":834},66,[32,836,381],{"class":134},[32,838,384],{"class":62},[32,840,387],{"class":58},[32,842,843],{"class":155},"\"  multiprocessing speedup: ",[32,845,393],{"class":134},[32,847,396],{"class":62},[32,849,438],{"class":58},[32,851,794],{"class":62},[32,853,443],{"class":58},[32,855,402],{"class":134},[32,857,858],{"class":155},"x (≈workers — true parallelism)\"",[32,860,189],{"class":62},[32,862,864],{"class":34,"line":863},67,[32,865,52],{"emptyLinePlaceholder":51},[32,867,869,872,875,878,881,884],{"class":34,"line":868},68,[32,870,871],{"class":58},"if",[32,873,874],{"class":134}," __name__",[32,876,877],{"class":58}," ==",[32,879,880],{"class":155}," \"__main__\"",[32,882,883],{"class":62},":   ",[32,885,886],{"class":38},"# REQUIRED for ProcessPoolExecutor (spawn re-imports module)\n",[32,888,890,893,896,898,901,903,906,908,911],{"class":34,"line":889},69,[32,891,892],{"class":62},"    cpu_bound_benchmark(",[32,894,895],{"class":180},"n",[32,897,174],{"class":58},[32,899,900],{"class":134},"5_000_000",[32,902,143],{"class":62},[32,904,905],{"class":180},"workers",[32,907,174],{"class":58},[32,909,910],{"class":134},"4",[32,912,189],{"class":62},[19,914,915],{"language":21},[23,916,918],{"className":25,"code":917,"language":21,"meta":27,"style":27},"# ── Race condition: demonstrating the lost-update problem ──\n# counter += 1 is NOT atomic — it's LOAD + ADD + STORE, interruptible between any step.\n\nimport threading\n\ncounter = 0\nlock = threading.Lock()\n\ndef increment_unsafe(n: int):\n    \"\"\"RACE CONDITION: counter += 1 compiles to multiple bytecodes, GIL can switch mid-way.\"\"\"\n    global counter\n    for _ in range(n):\n        counter += 1   # LOAD_FAST counter; LOAD_CONST 1; BINARY_OP +=; STORE_FAST counter\n                       # GIL can release between LOAD and STORE → two threads read same old value\n\ndef increment_safe(n: int, lock: threading.Lock):\n    \"\"\"CORRECT: lock serializes the read-modify-write, no interleaving possible.\"\"\"\n    global counter\n    for _ in range(n):\n        with lock:           # acquire on entry, release on exit (even on exception)\n            counter += 1     # only one thread can be inside this block at a time\n\n# Demonstrate the race\ncounter = 0\nthreads = [threading.Thread(target=increment_unsafe, args=(100_000,)) for _ in range(4)]\nfor t in threads: t.start()\nfor t in threads: t.join()\nprint(f\"Unsafe (expected 400000): {counter}\")   # e.g. 387,341 — lost updates!\n\n# Demonstrate the fix\ncounter = 0\nlock = threading.Lock()\nthreads = [threading.Thread(target=increment_safe, args=(100_000, lock)) for _ in range(4)]\nfor t in threads: t.start()\nfor t in threads: t.join()\nprint(f\"Safe (expected 400000):   {counter}\")   # 400000 — always correct\n",[29,919,920,925,930,934,940,944,953,963,967,980,985,993,1006,1019,1024,1028,1042,1047,1053,1065,1076,1088,1092,1097,1105,1152,1164,1175,1203,1207,1212,1220,1228,1268,1278,1288],{"__ignoreMap":27},[32,921,922],{"class":34,"line":35},[32,923,924],{"class":38},"# ── Race condition: demonstrating the lost-update problem ──\n",[32,926,927],{"class":34,"line":42},[32,928,929],{"class":38},"# counter += 1 is NOT atomic — it's LOAD + ADD + STORE, interruptible between any step.\n",[32,931,932],{"class":34,"line":48},[32,933,52],{"emptyLinePlaceholder":51},[32,935,936,938],{"class":34,"line":55},[32,937,59],{"class":58},[32,939,63],{"class":62},[32,941,942],{"class":34,"line":66},[32,943,52],{"emptyLinePlaceholder":51},[32,945,946,949,951],{"class":34,"line":80},[32,947,948],{"class":62},"counter ",[32,950,174],{"class":58},[32,952,498],{"class":134},[32,954,955,958,960],{"class":34,"line":88},[32,956,957],{"class":62},"lock ",[32,959,174],{"class":58},[32,961,962],{"class":62}," threading.Lock()\n",[32,964,965],{"class":34,"line":96},[32,966,52],{"emptyLinePlaceholder":51},[32,968,969,971,974,976,978],{"class":34,"line":110},[32,970,124],{"class":58},[32,972,973],{"class":127}," increment_unsafe",[32,975,472],{"class":62},[32,977,146],{"class":134},[32,979,565],{"class":62},[32,981,982],{"class":34,"line":115},[32,983,984],{"class":155},"    \"\"\"RACE CONDITION: counter += 1 compiles to multiple bytecodes, GIL can switch mid-way.\"\"\"\n",[32,986,987,990],{"class":34,"line":121},[32,988,989],{"class":58},"    global",[32,991,992],{"class":62}," counter\n",[32,994,995,997,1000,1002,1004],{"class":34,"line":152},[32,996,271],{"class":58},[32,998,999],{"class":62}," _ ",[32,1001,277],{"class":58},[32,1003,511],{"class":134},[32,1005,514],{"class":62},[32,1007,1008,1011,1013,1016],{"class":34,"line":159},[32,1009,1010],{"class":62},"        counter ",[32,1012,523],{"class":58},[32,1014,1015],{"class":134}," 1",[32,1017,1018],{"class":38},"   # LOAD_FAST counter; LOAD_CONST 1; BINARY_OP +=; STORE_FAST counter\n",[32,1020,1021],{"class":34,"line":168},[32,1022,1023],{"class":38},"                       # GIL can release between LOAD and STORE → two threads read same old value\n",[32,1025,1026],{"class":34,"line":192},[32,1027,52],{"emptyLinePlaceholder":51},[32,1029,1030,1032,1035,1037,1039],{"class":34,"line":201},[32,1031,124],{"class":58},[32,1033,1034],{"class":127}," increment_safe",[32,1036,472],{"class":62},[32,1038,146],{"class":134},[32,1040,1041],{"class":62},", lock: threading.Lock):\n",[32,1043,1044],{"class":34,"line":216},[32,1045,1046],{"class":155},"    \"\"\"CORRECT: lock serializes the read-modify-write, no interleaving possible.\"\"\"\n",[32,1048,1049,1051],{"class":34,"line":230},[32,1050,989],{"class":58},[32,1052,992],{"class":62},[32,1054,1055,1057,1059,1061,1063],{"class":34,"line":235},[32,1056,271],{"class":58},[32,1058,999],{"class":62},[32,1060,277],{"class":58},[32,1062,511],{"class":134},[32,1064,514],{"class":62},[32,1066,1067,1070,1073],{"class":34,"line":251},[32,1068,1069],{"class":58},"        with",[32,1071,1072],{"class":62}," lock:           ",[32,1074,1075],{"class":38},"# acquire on entry, release on exit (even on exception)\n",[32,1077,1078,1081,1083,1085],{"class":34,"line":257},[32,1079,1080],{"class":62},"            counter ",[32,1082,523],{"class":58},[32,1084,1015],{"class":134},[32,1086,1087],{"class":38},"     # only one thread can be inside this block at a time\n",[32,1089,1090],{"class":34,"line":268},[32,1091,52],{"emptyLinePlaceholder":51},[32,1093,1094],{"class":34,"line":283},[32,1095,1096],{"class":38},"# Demonstrate the race\n",[32,1098,1099,1101,1103],{"class":34,"line":289},[32,1100,948],{"class":62},[32,1102,174],{"class":58},[32,1104,498],{"class":134},[32,1106,1107,1110,1112,1115,1118,1120,1123,1126,1128,1130,1133,1136,1139,1141,1143,1145,1147,1149],{"class":34,"line":305},[32,1108,1109],{"class":62},"threads ",[32,1111,174],{"class":58},[32,1113,1114],{"class":62}," [threading.Thread(",[32,1116,1117],{"class":180},"target",[32,1119,174],{"class":58},[32,1121,1122],{"class":62},"increment_unsafe, ",[32,1124,1125],{"class":180},"args",[32,1127,174],{"class":58},[32,1129,384],{"class":62},[32,1131,1132],{"class":134},"100_000",[32,1134,1135],{"class":62},",)) ",[32,1137,1138],{"class":58},"for",[32,1140,999],{"class":62},[32,1142,277],{"class":58},[32,1144,511],{"class":134},[32,1146,384],{"class":62},[32,1148,910],{"class":134},[32,1150,1151],{"class":62},")]\n",[32,1153,1154,1156,1159,1161],{"class":34,"line":310},[32,1155,1138],{"class":58},[32,1157,1158],{"class":62}," t ",[32,1160,277],{"class":58},[32,1162,1163],{"class":62}," threads: t.start()\n",[32,1165,1166,1168,1170,1172],{"class":34,"line":316},[32,1167,1138],{"class":58},[32,1169,1158],{"class":62},[32,1171,277],{"class":58},[32,1173,1174],{"class":62}," threads: t.join()\n",[32,1176,1177,1180,1182,1184,1187,1189,1192,1194,1197,1200],{"class":34,"line":325},[32,1178,1179],{"class":134},"print",[32,1181,384],{"class":62},[32,1183,387],{"class":58},[32,1185,1186],{"class":155},"\"Unsafe (expected 400000): ",[32,1188,393],{"class":134},[32,1190,1191],{"class":62},"counter",[32,1193,402],{"class":134},[32,1195,1196],{"class":155},"\"",[32,1198,1199],{"class":62},")   ",[32,1201,1202],{"class":38},"# e.g. 387,341 — lost updates!\n",[32,1204,1205],{"class":34,"line":350},[32,1206,52],{"emptyLinePlaceholder":51},[32,1208,1209],{"class":34,"line":359},[32,1210,1211],{"class":38},"# Demonstrate the fix\n",[32,1213,1214,1216,1218],{"class":34,"line":373},[32,1215,948],{"class":62},[32,1217,174],{"class":58},[32,1219,498],{"class":134},[32,1221,1222,1224,1226],{"class":34,"line":378},[32,1223,957],{"class":62},[32,1225,174],{"class":58},[32,1227,962],{"class":62},[32,1229,1230,1232,1234,1236,1238,1240,1243,1245,1247,1249,1251,1254,1256,1258,1260,1262,1264,1266],{"class":34,"line":422},[32,1231,1109],{"class":62},[32,1233,174],{"class":58},[32,1235,1114],{"class":62},[32,1237,1117],{"class":180},[32,1239,174],{"class":58},[32,1241,1242],{"class":62},"increment_safe, ",[32,1244,1125],{"class":180},[32,1246,174],{"class":58},[32,1248,384],{"class":62},[32,1250,1132],{"class":134},[32,1252,1253],{"class":62},", lock)) ",[32,1255,1138],{"class":58},[32,1257,999],{"class":62},[32,1259,277],{"class":58},[32,1261,511],{"class":134},[32,1263,384],{"class":62},[32,1265,910],{"class":134},[32,1267,1151],{"class":62},[32,1269,1270,1272,1274,1276],{"class":34,"line":453},[32,1271,1138],{"class":58},[32,1273,1158],{"class":62},[32,1275,277],{"class":58},[32,1277,1163],{"class":62},[32,1279,1280,1282,1284,1286],{"class":34,"line":458},[32,1281,1138],{"class":58},[32,1283,1158],{"class":62},[32,1285,277],{"class":58},[32,1287,1174],{"class":62},[32,1289,1290,1292,1294,1296,1299,1301,1303,1305,1307,1309],{"class":34,"line":464},[32,1291,1179],{"class":134},[32,1293,384],{"class":62},[32,1295,387],{"class":58},[32,1297,1298],{"class":155},"\"Safe (expected 400000):   ",[32,1300,393],{"class":134},[32,1302,1191],{"class":62},[32,1304,402],{"class":134},[32,1306,1196],{"class":155},[32,1308,1199],{"class":62},[32,1310,1311],{"class":38},"# 400000 — always correct\n",[14,1313,1315,1318],{"id":1314},"threading-shared-memory-explicit-locks",[29,1316,1317],{},"threading"," — Shared Memory, Explicit Locks",[19,1320,1321],{"language":21},[23,1322,1324],{"className":25,"code":1323,"language":21,"meta":27,"style":27},"import threading\n\ncounter = 0\n\ndef increment_unsafe():\n    global counter\n    for _ in range(100_000):\n        counter += 1     # NOT atomic! read-modify-write across three bytecode ops, interruptible mid-way\n\nthreads = [threading.Thread(target=increment_unsafe) for _ in range(4)]\nfor t in threads:\n    t.start()\nfor t in threads:\n    t.join()\nprint(counter)   # expected 400,000 — often prints something LESS, e.g. 391,842 — a lost-update race condition\n",[29,1325,1326,1332,1336,1344,1348,1357,1363,1379,1390,1394,1423,1434,1439,1449,1454],{"__ignoreMap":27},[32,1327,1328,1330],{"class":34,"line":35},[32,1329,59],{"class":58},[32,1331,63],{"class":62},[32,1333,1334],{"class":34,"line":42},[32,1335,52],{"emptyLinePlaceholder":51},[32,1337,1338,1340,1342],{"class":34,"line":48},[32,1339,948],{"class":62},[32,1341,174],{"class":58},[32,1343,498],{"class":134},[32,1345,1346],{"class":34,"line":55},[32,1347,52],{"emptyLinePlaceholder":51},[32,1349,1350,1352,1354],{"class":34,"line":66},[32,1351,124],{"class":58},[32,1353,973],{"class":127},[32,1355,1356],{"class":62},"():\n",[32,1358,1359,1361],{"class":34,"line":80},[32,1360,989],{"class":58},[32,1362,992],{"class":62},[32,1364,1365,1367,1369,1371,1373,1375,1377],{"class":34,"line":88},[32,1366,271],{"class":58},[32,1368,999],{"class":62},[32,1370,277],{"class":58},[32,1372,511],{"class":134},[32,1374,384],{"class":62},[32,1376,1132],{"class":134},[32,1378,565],{"class":62},[32,1380,1381,1383,1385,1387],{"class":34,"line":96},[32,1382,1010],{"class":62},[32,1384,523],{"class":58},[32,1386,1015],{"class":134},[32,1388,1389],{"class":38},"     # NOT atomic! read-modify-write across three bytecode ops, interruptible mid-way\n",[32,1391,1392],{"class":34,"line":110},[32,1393,52],{"emptyLinePlaceholder":51},[32,1395,1396,1398,1400,1402,1404,1406,1409,1411,1413,1415,1417,1419,1421],{"class":34,"line":115},[32,1397,1109],{"class":62},[32,1399,174],{"class":58},[32,1401,1114],{"class":62},[32,1403,1117],{"class":180},[32,1405,174],{"class":58},[32,1407,1408],{"class":62},"increment_unsafe) ",[32,1410,1138],{"class":58},[32,1412,999],{"class":62},[32,1414,277],{"class":58},[32,1416,511],{"class":134},[32,1418,384],{"class":62},[32,1420,910],{"class":134},[32,1422,1151],{"class":62},[32,1424,1425,1427,1429,1431],{"class":34,"line":121},[32,1426,1138],{"class":58},[32,1428,1158],{"class":62},[32,1430,277],{"class":58},[32,1432,1433],{"class":62}," threads:\n",[32,1435,1436],{"class":34,"line":152},[32,1437,1438],{"class":62},"    t.start()\n",[32,1440,1441,1443,1445,1447],{"class":34,"line":159},[32,1442,1138],{"class":58},[32,1444,1158],{"class":62},[32,1446,277],{"class":58},[32,1448,1433],{"class":62},[32,1450,1451],{"class":34,"line":168},[32,1452,1453],{"class":62},"    t.join()\n",[32,1455,1456,1458,1461],{"class":34,"line":192},[32,1457,1179],{"class":134},[32,1459,1460],{"class":62},"(counter)   ",[32,1462,1463],{"class":38},"# expected 400,000 — often prints something LESS, e.g. 391,842 — a lost-update race condition\n",[1465,1466,1467,1468,1472,1473,1476,1477,1480],"p",{},"This is a ",[1469,1470,1471],"strong",{},"race condition",": ",[29,1474,1475],{},"counter += 1"," is not a single atomic operation — it compiles to a load, an add, and a store, and the GIL can switch threads between any of those steps. Two threads can both read the same old value before either writes back the incremented result, silently losing an update. The fix is a ",[29,1478,1479],{},"Lock",":",[19,1482,1483],{"language":21},[23,1484,1486],{"className":25,"code":1485,"language":21,"meta":27,"style":27},"import threading\n\ncounter = 0\nlock = threading.Lock()\n\ndef increment_safe():\n    global counter\n    for _ in range(100_000):\n        with lock:            # only ONE thread can hold the lock at a time\n            counter += 1\n\nthreads = [threading.Thread(target=increment_safe) for _ in range(4)]\nfor t in threads:\n    t.start()\nfor t in threads:\n    t.join()\nprint(counter)   # always exactly 400,000\n",[29,1487,1488,1494,1498,1506,1514,1518,1526,1532,1548,1558,1567,1571,1600,1610,1614,1624,1628],{"__ignoreMap":27},[32,1489,1490,1492],{"class":34,"line":35},[32,1491,59],{"class":58},[32,1493,63],{"class":62},[32,1495,1496],{"class":34,"line":42},[32,1497,52],{"emptyLinePlaceholder":51},[32,1499,1500,1502,1504],{"class":34,"line":48},[32,1501,948],{"class":62},[32,1503,174],{"class":58},[32,1505,498],{"class":134},[32,1507,1508,1510,1512],{"class":34,"line":55},[32,1509,957],{"class":62},[32,1511,174],{"class":58},[32,1513,962],{"class":62},[32,1515,1516],{"class":34,"line":66},[32,1517,52],{"emptyLinePlaceholder":51},[32,1519,1520,1522,1524],{"class":34,"line":80},[32,1521,124],{"class":58},[32,1523,1034],{"class":127},[32,1525,1356],{"class":62},[32,1527,1528,1530],{"class":34,"line":88},[32,1529,989],{"class":58},[32,1531,992],{"class":62},[32,1533,1534,1536,1538,1540,1542,1544,1546],{"class":34,"line":96},[32,1535,271],{"class":58},[32,1537,999],{"class":62},[32,1539,277],{"class":58},[32,1541,511],{"class":134},[32,1543,384],{"class":62},[32,1545,1132],{"class":134},[32,1547,565],{"class":62},[32,1549,1550,1552,1555],{"class":34,"line":110},[32,1551,1069],{"class":58},[32,1553,1554],{"class":62}," lock:            ",[32,1556,1557],{"class":38},"# only ONE thread can hold the lock at a time\n",[32,1559,1560,1562,1564],{"class":34,"line":115},[32,1561,1080],{"class":62},[32,1563,523],{"class":58},[32,1565,1566],{"class":134}," 1\n",[32,1568,1569],{"class":34,"line":121},[32,1570,52],{"emptyLinePlaceholder":51},[32,1572,1573,1575,1577,1579,1581,1583,1586,1588,1590,1592,1594,1596,1598],{"class":34,"line":152},[32,1574,1109],{"class":62},[32,1576,174],{"class":58},[32,1578,1114],{"class":62},[32,1580,1117],{"class":180},[32,1582,174],{"class":58},[32,1584,1585],{"class":62},"increment_safe) ",[32,1587,1138],{"class":58},[32,1589,999],{"class":62},[32,1591,277],{"class":58},[32,1593,511],{"class":134},[32,1595,384],{"class":62},[32,1597,910],{"class":134},[32,1599,1151],{"class":62},[32,1601,1602,1604,1606,1608],{"class":34,"line":159},[32,1603,1138],{"class":58},[32,1605,1158],{"class":62},[32,1607,277],{"class":58},[32,1609,1433],{"class":62},[32,1611,1612],{"class":34,"line":168},[32,1613,1438],{"class":62},[32,1615,1616,1618,1620,1622],{"class":34,"line":192},[32,1617,1138],{"class":58},[32,1619,1158],{"class":62},[32,1621,277],{"class":58},[32,1623,1433],{"class":62},[32,1625,1626],{"class":34,"line":201},[32,1627,1453],{"class":62},[32,1629,1630,1632,1634],{"class":34,"line":216},[32,1631,1179],{"class":134},[32,1633,1460],{"class":62},[32,1635,1636],{"class":38},"# always exactly 400,000\n",[1465,1638,1639,1642,1643,438,1646,1649,1650,1653],{},[29,1640,1641],{},"with lock:"," acquires on entry and releases on exit (even if an exception is raised inside), exactly like the context managers from chapters 18-19 — never call ",[29,1644,1645],{},"lock.acquire()",[29,1647,1648],{},"lock.release()"," manually, since a missed ",[29,1651,1652],{},"release()"," on an exception path causes every other thread waiting on that lock to hang forever (a deadlock).",[1655,1656,1658,143,1661,1664,1665],"h3",{"id":1657},"rlock-condition-and-event",[29,1659,1660],{},"RLock",[29,1662,1663],{},"Condition",", and ",[29,1666,1667],{},"Event",[19,1669,1670],{"language":21},[23,1671,1673],{"className":25,"code":1672,"language":21,"meta":27,"style":27},"import threading\n\n# RLock — reentrant: the SAME thread can acquire it multiple times without deadlocking itself\nrlock = threading.RLock()\n\ndef outer():\n    with rlock:\n        inner()             # inner() also acquires rlock — fine, same thread, RLock tracks a counter\n\ndef inner():\n    with rlock:\n        print(\"inner\")\n\n# A plain Lock here would deadlock: the second `with lock` in the same thread would block forever\n# waiting for a lock the SAME thread already holds and will never release until this call returns.\n",[29,1674,1675,1681,1685,1690,1700,1704,1713,1720,1728,1732,1741,1747,1759,1763,1768],{"__ignoreMap":27},[32,1676,1677,1679],{"class":34,"line":35},[32,1678,59],{"class":58},[32,1680,63],{"class":62},[32,1682,1683],{"class":34,"line":42},[32,1684,52],{"emptyLinePlaceholder":51},[32,1686,1687],{"class":34,"line":48},[32,1688,1689],{"class":38},"# RLock — reentrant: the SAME thread can acquire it multiple times without deadlocking itself\n",[32,1691,1692,1695,1697],{"class":34,"line":55},[32,1693,1694],{"class":62},"rlock ",[32,1696,174],{"class":58},[32,1698,1699],{"class":62}," threading.RLock()\n",[32,1701,1702],{"class":34,"line":66},[32,1703,52],{"emptyLinePlaceholder":51},[32,1705,1706,1708,1711],{"class":34,"line":80},[32,1707,124],{"class":58},[32,1709,1710],{"class":127}," outer",[32,1712,1356],{"class":62},[32,1714,1715,1717],{"class":34,"line":88},[32,1716,328],{"class":58},[32,1718,1719],{"class":62}," rlock:\n",[32,1721,1722,1725],{"class":34,"line":96},[32,1723,1724],{"class":62},"        inner()             ",[32,1726,1727],{"class":38},"# inner() also acquires rlock — fine, same thread, RLock tracks a counter\n",[32,1729,1730],{"class":34,"line":110},[32,1731,52],{"emptyLinePlaceholder":51},[32,1733,1734,1736,1739],{"class":34,"line":115},[32,1735,124],{"class":58},[32,1737,1738],{"class":127}," inner",[32,1740,1356],{"class":62},[32,1742,1743,1745],{"class":34,"line":121},[32,1744,328],{"class":58},[32,1746,1719],{"class":62},[32,1748,1749,1752,1754,1757],{"class":34,"line":152},[32,1750,1751],{"class":134},"        print",[32,1753,384],{"class":62},[32,1755,1756],{"class":155},"\"inner\"",[32,1758,189],{"class":62},[32,1760,1761],{"class":34,"line":159},[32,1762,52],{"emptyLinePlaceholder":51},[32,1764,1765],{"class":34,"line":168},[32,1766,1767],{"class":38},"# A plain Lock here would deadlock: the second `with lock` in the same thread would block forever\n",[32,1769,1770],{"class":34,"line":192},[32,1771,1772],{"class":38},"# waiting for a lock the SAME thread already holds and will never release until this call returns.\n",[19,1774,1775],{"language":21},[23,1776,1778],{"className":25,"code":1777,"language":21,"meta":27,"style":27},"import threading\nimport time\n\nready = threading.Event()\n\ndef waiter():\n    print(\"waiting for signal...\")\n    ready.wait()            # blocks until .set() is called from any thread\n    print(\"signal received, proceeding\")\n\ndef setter():\n    time.sleep(1)\n    ready.set()\n\nthreading.Thread(target=waiter).start()\nthreading.Thread(target=setter).start()\n",[29,1779,1780,1786,1792,1796,1806,1810,1819,1830,1838,1849,1853,1862,1872,1877,1881,1893],{"__ignoreMap":27},[32,1781,1782,1784],{"class":34,"line":35},[32,1783,59],{"class":58},[32,1785,63],{"class":62},[32,1787,1788,1790],{"class":34,"line":42},[32,1789,59],{"class":58},[32,1791,85],{"class":62},[32,1793,1794],{"class":34,"line":48},[32,1795,52],{"emptyLinePlaceholder":51},[32,1797,1798,1801,1803],{"class":34,"line":55},[32,1799,1800],{"class":62},"ready ",[32,1802,174],{"class":58},[32,1804,1805],{"class":62}," threading.Event()\n",[32,1807,1808],{"class":34,"line":66},[32,1809,52],{"emptyLinePlaceholder":51},[32,1811,1812,1814,1817],{"class":34,"line":80},[32,1813,124],{"class":58},[32,1815,1816],{"class":127}," waiter",[32,1818,1356],{"class":62},[32,1820,1821,1823,1825,1828],{"class":34,"line":88},[32,1822,381],{"class":134},[32,1824,384],{"class":62},[32,1826,1827],{"class":155},"\"waiting for signal...\"",[32,1829,189],{"class":62},[32,1831,1832,1835],{"class":34,"line":96},[32,1833,1834],{"class":62},"    ready.wait()            ",[32,1836,1837],{"class":38},"# blocks until .set() is called from any thread\n",[32,1839,1840,1842,1844,1847],{"class":34,"line":110},[32,1841,381],{"class":134},[32,1843,384],{"class":62},[32,1845,1846],{"class":155},"\"signal received, proceeding\"",[32,1848,189],{"class":62},[32,1850,1851],{"class":34,"line":115},[32,1852,52],{"emptyLinePlaceholder":51},[32,1854,1855,1857,1860],{"class":34,"line":121},[32,1856,124],{"class":58},[32,1858,1859],{"class":127}," setter",[32,1861,1356],{"class":62},[32,1863,1864,1867,1870],{"class":34,"line":152},[32,1865,1866],{"class":62},"    time.sleep(",[32,1868,1869],{"class":134},"1",[32,1871,189],{"class":62},[32,1873,1874],{"class":34,"line":159},[32,1875,1876],{"class":62},"    ready.set()\n",[32,1878,1879],{"class":34,"line":168},[32,1880,52],{"emptyLinePlaceholder":51},[32,1882,1883,1886,1888,1890],{"class":34,"line":192},[32,1884,1885],{"class":62},"threading.Thread(",[32,1887,1117],{"class":180},[32,1889,174],{"class":58},[32,1891,1892],{"class":62},"waiter).start()\n",[32,1894,1895,1897,1899,1901],{"class":34,"line":201},[32,1896,1885],{"class":62},[32,1898,1117],{"class":180},[32,1900,174],{"class":58},[32,1902,1903],{"class":62},"setter).start()\n",[1465,1905,1906,1908,1909,1912,1913,1916,1917,1919,1920,1923,1924,1927],{},[29,1907,1667],{}," is the simplest cross-thread signaling primitive — one or more threads ",[29,1910,1911],{},"wait()",", another thread calls ",[29,1914,1915],{},"set()",", and all waiters wake up. ",[29,1918,1663],{}," extends this with the ability to wait for an arbitrary predicate and notify one (",[29,1921,1922],{},"notify()",") or all (",[29,1925,1926],{},"notify_all()",") waiters, the standard tool for producer\u002Fconsumer patterns.",[14,1929,1931,1934],{"id":1930},"queuequeue-thread-safe-producerconsumer",[29,1932,1933],{},"queue.Queue"," — Thread-Safe Producer\u002FConsumer",[19,1936,1937],{"language":21},[23,1938,1940],{"className":25,"code":1939,"language":21,"meta":27,"style":27},"import threading\nimport queue\n\ntask_queue = queue.Queue()\n\ndef producer():\n    for i in range(5):\n        task_queue.put(i)\n    task_queue.put(None)      # sentinel value signaling \"no more work\"\n\ndef consumer():\n    while (item := task_queue.get()) is not None:\n        print(f\"processing {item}\")\n        task_queue.task_done()\n    task_queue.task_done()\n\nthreading.Thread(target=producer).start()\nthreading.Thread(target=consumer).start()\n",[29,1941,1942,1948,1955,1959,1969,1973,1982,1998,2003,2017,2021,2030,2055,2077,2082,2087,2091,2102],{"__ignoreMap":27},[32,1943,1944,1946],{"class":34,"line":35},[32,1945,59],{"class":58},[32,1947,63],{"class":62},[32,1949,1950,1952],{"class":34,"line":42},[32,1951,59],{"class":58},[32,1953,1954],{"class":62}," queue\n",[32,1956,1957],{"class":34,"line":48},[32,1958,52],{"emptyLinePlaceholder":51},[32,1960,1961,1964,1966],{"class":34,"line":55},[32,1962,1963],{"class":62},"task_queue ",[32,1965,174],{"class":58},[32,1967,1968],{"class":62}," queue.Queue()\n",[32,1970,1971],{"class":34,"line":66},[32,1972,52],{"emptyLinePlaceholder":51},[32,1974,1975,1977,1980],{"class":34,"line":80},[32,1976,124],{"class":58},[32,1978,1979],{"class":127}," producer",[32,1981,1356],{"class":62},[32,1983,1984,1986,1988,1990,1992,1994,1996],{"class":34,"line":88},[32,1985,271],{"class":58},[32,1987,506],{"class":62},[32,1989,277],{"class":58},[32,1991,511],{"class":134},[32,1993,384],{"class":62},[32,1995,186],{"class":134},[32,1997,565],{"class":62},[32,1999,2000],{"class":34,"line":96},[32,2001,2002],{"class":62},"        task_queue.put(i)\n",[32,2004,2005,2008,2011,2014],{"class":34,"line":110},[32,2006,2007],{"class":62},"    task_queue.put(",[32,2009,2010],{"class":134},"None",[32,2012,2013],{"class":62},")      ",[32,2015,2016],{"class":38},"# sentinel value signaling \"no more work\"\n",[32,2018,2019],{"class":34,"line":115},[32,2020,52],{"emptyLinePlaceholder":51},[32,2022,2023,2025,2028],{"class":34,"line":121},[32,2024,124],{"class":58},[32,2026,2027],{"class":127}," consumer",[32,2029,1356],{"class":62},[32,2031,2032,2035,2038,2041,2044,2047,2050,2053],{"class":34,"line":152},[32,2033,2034],{"class":58},"    while",[32,2036,2037],{"class":62}," (item ",[32,2039,2040],{"class":58},":=",[32,2042,2043],{"class":62}," task_queue.get()) ",[32,2045,2046],{"class":58},"is",[32,2048,2049],{"class":58}," not",[32,2051,2052],{"class":134}," None",[32,2054,165],{"class":62},[32,2056,2057,2059,2061,2063,2066,2068,2071,2073,2075],{"class":34,"line":159},[32,2058,1751],{"class":134},[32,2060,384],{"class":62},[32,2062,387],{"class":58},[32,2064,2065],{"class":155},"\"processing ",[32,2067,393],{"class":134},[32,2069,2070],{"class":62},"item",[32,2072,402],{"class":134},[32,2074,1196],{"class":155},[32,2076,189],{"class":62},[32,2078,2079],{"class":34,"line":168},[32,2080,2081],{"class":62},"        task_queue.task_done()\n",[32,2083,2084],{"class":34,"line":192},[32,2085,2086],{"class":62},"    task_queue.task_done()\n",[32,2088,2089],{"class":34,"line":201},[32,2090,52],{"emptyLinePlaceholder":51},[32,2092,2093,2095,2097,2099],{"class":34,"line":216},[32,2094,1885],{"class":62},[32,2096,1117],{"class":180},[32,2098,174],{"class":58},[32,2100,2101],{"class":62},"producer).start()\n",[32,2103,2104,2106,2108,2110],{"class":34,"line":230},[32,2105,1885],{"class":62},[32,2107,1117],{"class":180},[32,2109,174],{"class":58},[32,2111,2112],{"class":62},"consumer).start()\n",[1465,2114,2115,2117,2118,438,2121,2124,2125,2127],{},[29,2116,1933],{}," handles all locking internally — ",[29,2119,2120],{},"put()",[29,2122,2123],{},"get()"," are safe to call concurrently from any number of threads without a manual ",[29,2126,1479],{},", making it the preferred way to hand data between threads instead of shared mutable variables protected by hand-managed locks.",[14,2129,2131,2134],{"id":2130},"concurrentfutures-the-high-level-pool-api",[29,2132,2133],{},"concurrent.futures"," — The High-Level Pool API",[19,2136,2137],{"language":21},[23,2138,2140],{"className":25,"code":2139,"language":21,"meta":27,"style":27},"from concurrent.futures import ThreadPoolExecutor, as_completed\nimport requests\n\nurls = [\"https:\u002F\u002Fexample.com\"] * 5\n\nwith ThreadPoolExecutor(max_workers=5) as executor:\n    futures = {executor.submit(requests.get, url): url for url in urls}\n    for future in as_completed(futures):\n        url = futures[future]\n        try:\n            response = future.result()\n            print(f\"{url}: {response.status_code}\")\n        except Exception as exc:\n            print(f\"{url} raised {exc!r}\")\n",[29,2141,2142,2153,2159,2163,2184,2188,2208,2227,2239,2249,2256,2266,2297,2309],{"__ignoreMap":27},[32,2143,2144,2146,2148,2150],{"class":34,"line":35},[32,2145,99],{"class":58},[32,2147,102],{"class":62},[32,2149,59],{"class":58},[32,2151,2152],{"class":62}," ThreadPoolExecutor, as_completed\n",[32,2154,2155,2157],{"class":34,"line":42},[32,2156,59],{"class":58},[32,2158,93],{"class":62},[32,2160,2161],{"class":34,"line":48},[32,2162,52],{"emptyLinePlaceholder":51},[32,2164,2165,2168,2170,2173,2176,2179,2181],{"class":34,"line":55},[32,2166,2167],{"class":62},"urls ",[32,2169,174],{"class":58},[32,2171,2172],{"class":62}," [",[32,2174,2175],{"class":155},"\"https:\u002F\u002Fexample.com\"",[32,2177,2178],{"class":62},"] ",[32,2180,528],{"class":58},[32,2182,2183],{"class":134}," 5\n",[32,2185,2186],{"class":34,"line":66},[32,2187,52],{"emptyLinePlaceholder":51},[32,2189,2190,2193,2195,2197,2199,2201,2203,2205],{"class":34,"line":80},[32,2191,2192],{"class":58},"with",[32,2194,331],{"class":62},[32,2196,334],{"class":180},[32,2198,174],{"class":58},[32,2200,186],{"class":134},[32,2202,342],{"class":62},[32,2204,74],{"class":58},[32,2206,2207],{"class":62}," executor:\n",[32,2209,2210,2213,2215,2218,2220,2222,2224],{"class":34,"line":88},[32,2211,2212],{"class":62},"    futures ",[32,2214,174],{"class":58},[32,2216,2217],{"class":62}," {executor.submit(requests.get, url): url ",[32,2219,1138],{"class":58},[32,2221,274],{"class":62},[32,2223,277],{"class":58},[32,2225,2226],{"class":62}," urls}\n",[32,2228,2229,2231,2234,2236],{"class":34,"line":96},[32,2230,271],{"class":58},[32,2232,2233],{"class":62}," future ",[32,2235,277],{"class":58},[32,2237,2238],{"class":62}," as_completed(futures):\n",[32,2240,2241,2244,2246],{"class":34,"line":110},[32,2242,2243],{"class":62},"        url ",[32,2245,174],{"class":58},[32,2247,2248],{"class":62}," futures[future]\n",[32,2250,2251,2254],{"class":34,"line":115},[32,2252,2253],{"class":58},"        try",[32,2255,165],{"class":62},[32,2257,2258,2261,2263],{"class":34,"line":121},[32,2259,2260],{"class":62},"            response ",[32,2262,174],{"class":58},[32,2264,2265],{"class":62}," future.result()\n",[32,2267,2268,2271,2273,2275,2277,2279,2282,2284,2286,2288,2291,2293,2295],{"class":34,"line":152},[32,2269,2270],{"class":134},"            print",[32,2272,384],{"class":62},[32,2274,387],{"class":58},[32,2276,1196],{"class":155},[32,2278,393],{"class":134},[32,2280,2281],{"class":62},"url",[32,2283,402],{"class":134},[32,2285,1472],{"class":155},[32,2287,393],{"class":134},[32,2289,2290],{"class":62},"response.status_code",[32,2292,402],{"class":134},[32,2294,1196],{"class":155},[32,2296,189],{"class":62},[32,2298,2299,2302,2304,2306],{"class":34,"line":159},[32,2300,2301],{"class":58},"        except",[32,2303,207],{"class":134},[32,2305,210],{"class":58},[32,2307,2308],{"class":62}," exc:\n",[32,2310,2311,2313,2315,2317,2319,2321,2323,2325,2328,2330,2333,2336,2338,2340],{"class":34,"line":168},[32,2312,2270],{"class":134},[32,2314,384],{"class":62},[32,2316,387],{"class":58},[32,2318,1196],{"class":155},[32,2320,393],{"class":134},[32,2322,2281],{"class":62},[32,2324,402],{"class":134},[32,2326,2327],{"class":155}," raised ",[32,2329,393],{"class":134},[32,2331,2332],{"class":62},"exc",[32,2334,2335],{"class":58},"!r",[32,2337,402],{"class":134},[32,2339,1196],{"class":155},[32,2341,189],{"class":62},[1465,2343,2344,2347,2348,2351,2352,2355,2356,2359,2360,2363],{},[29,2345,2346],{},"ThreadPoolExecutor"," manages a fixed pool of worker threads and a task queue for you — ",[29,2349,2350],{},"submit()"," returns a ",[29,2353,2354],{},"Future"," immediately (non-blocking), and ",[29,2357,2358],{},".result()"," blocks until that specific task completes, re-raising any exception the task raised. This is almost always preferable to manually creating and joining ",[29,2361,2362],{},"Thread"," objects for anything beyond a couple of one-off threads.",[14,2365,2367,2370],{"id":2366},"multiprocessing-true-parallelism-via-separate-processes",[29,2368,2369],{},"multiprocessing"," — True Parallelism via Separate Processes",[19,2372,2373],{"language":21},[23,2374,2376],{"className":25,"code":2375,"language":21,"meta":27,"style":27},"from multiprocessing import Process, Pool\nimport os\n\ndef cpu_bound_work(n):\n    total = 0\n    for i in range(n):\n        total += i\n    return total\n\nif __name__ == \"__main__\":     # REQUIRED on Windows\u002FmacOS spawn start method — see gotcha below\n    with Pool(processes=4) as pool:\n        results = pool.map(cpu_bound_work, [50_000_000] * 4)\n    print(results)   # genuinely runs on 4 cores in parallel — no GIL contention, separate interpreters\n",[29,2377,2378,2389,2396,2400,2409,2417,2429,2437,2443,2447,2463,2483,2505],{"__ignoreMap":27},[32,2379,2380,2382,2384,2386],{"class":34,"line":35},[32,2381,99],{"class":58},[32,2383,71],{"class":62},[32,2385,59],{"class":58},[32,2387,2388],{"class":62}," Process, Pool\n",[32,2390,2391,2393],{"class":34,"line":42},[32,2392,59],{"class":58},[32,2394,2395],{"class":62}," os\n",[32,2397,2398],{"class":34,"line":48},[32,2399,52],{"emptyLinePlaceholder":51},[32,2401,2402,2404,2407],{"class":34,"line":55},[32,2403,124],{"class":58},[32,2405,2406],{"class":127}," cpu_bound_work",[32,2408,514],{"class":62},[32,2410,2411,2413,2415],{"class":34,"line":66},[32,2412,493],{"class":62},[32,2414,174],{"class":58},[32,2416,498],{"class":134},[32,2418,2419,2421,2423,2425,2427],{"class":34,"line":80},[32,2420,271],{"class":58},[32,2422,506],{"class":62},[32,2424,277],{"class":58},[32,2426,511],{"class":134},[32,2428,514],{"class":62},[32,2430,2431,2433,2435],{"class":34,"line":88},[32,2432,520],{"class":62},[32,2434,523],{"class":58},[32,2436,531],{"class":62},[32,2438,2439,2441],{"class":34,"line":96},[32,2440,537],{"class":58},[32,2442,540],{"class":62},[32,2444,2445],{"class":34,"line":110},[32,2446,52],{"emptyLinePlaceholder":51},[32,2448,2449,2451,2453,2455,2457,2460],{"class":34,"line":115},[32,2450,871],{"class":58},[32,2452,874],{"class":134},[32,2454,877],{"class":58},[32,2456,880],{"class":155},[32,2458,2459],{"class":62},":     ",[32,2461,2462],{"class":38},"# REQUIRED on Windows\u002FmacOS spawn start method — see gotcha below\n",[32,2464,2465,2467,2470,2473,2475,2477,2479,2481],{"class":34,"line":121},[32,2466,328],{"class":58},[32,2468,2469],{"class":62}," Pool(",[32,2471,2472],{"class":180},"processes",[32,2474,174],{"class":58},[32,2476,910],{"class":134},[32,2478,342],{"class":62},[32,2480,74],{"class":58},[32,2482,347],{"class":62},[32,2484,2485,2488,2490,2493,2496,2498,2500,2503],{"class":34,"line":152},[32,2486,2487],{"class":62},"        results ",[32,2489,174],{"class":58},[32,2491,2492],{"class":62}," pool.map(cpu_bound_work, [",[32,2494,2495],{"class":134},"50_000_000",[32,2497,2178],{"class":62},[32,2499,528],{"class":58},[32,2501,2502],{"class":134}," 4",[32,2504,189],{"class":62},[32,2506,2507,2509,2512],{"class":34,"line":159},[32,2508,381],{"class":134},[32,2510,2511],{"class":62},"(results)   ",[32,2513,2514],{"class":38},"# genuinely runs on 4 cores in parallel — no GIL contention, separate interpreters\n",[1465,2516,2517,2518,2520,2521,2524,2525,2528],{},"Each process spawned by ",[29,2519,2369],{}," gets its ",[1469,2522,2523],{},"own Python interpreter and its own GIL"," — there is no shared memory and no lock contention between them, which is why CPU-bound work genuinely parallelizes across cores this way. The cost: data passed between processes must be ",[1469,2526,2527],{},"pickled"," (chapter 18) to cross the process boundary, which has real overhead for large objects, and processes don't share global variables or objects by default.",[19,2530,2531],{"language":21},[23,2532,2534],{"className":25,"code":2533,"language":21,"meta":27,"style":27},"from multiprocessing import Process, Value, Array, Manager\n\ndef worker(shared_counter, lock):\n    with lock:\n        shared_counter.value += 1\n\nif __name__ == \"__main__\":\n    from multiprocessing import Lock\n    counter = Value(\"i\", 0)          # 'i' = C int, allocated in SHARED memory across processes\n    lock = Lock()\n    processes = [Process(target=worker, args=(counter, lock)) for _ in range(10)]\n    for p in processes:\n        p.start()\n    for p in processes:\n        p.join()\n    print(counter.value)   # 10 — Value\u002FArray are the explicit mechanisms for sharing primitive state\n",[29,2535,2536,2547,2551,2561,2568,2577,2581,2593,2605,2629,2639,2677,2689,2694,2704,2709],{"__ignoreMap":27},[32,2537,2538,2540,2542,2544],{"class":34,"line":35},[32,2539,99],{"class":58},[32,2541,71],{"class":62},[32,2543,59],{"class":58},[32,2545,2546],{"class":62}," Process, Value, Array, Manager\n",[32,2548,2549],{"class":34,"line":42},[32,2550,52],{"emptyLinePlaceholder":51},[32,2552,2553,2555,2558],{"class":34,"line":48},[32,2554,124],{"class":58},[32,2556,2557],{"class":127}," worker",[32,2559,2560],{"class":62},"(shared_counter, lock):\n",[32,2562,2563,2565],{"class":34,"line":55},[32,2564,328],{"class":58},[32,2566,2567],{"class":62}," lock:\n",[32,2569,2570,2573,2575],{"class":34,"line":66},[32,2571,2572],{"class":62},"        shared_counter.value ",[32,2574,523],{"class":58},[32,2576,1566],{"class":134},[32,2578,2579],{"class":34,"line":80},[32,2580,52],{"emptyLinePlaceholder":51},[32,2582,2583,2585,2587,2589,2591],{"class":34,"line":88},[32,2584,871],{"class":58},[32,2586,874],{"class":134},[32,2588,877],{"class":58},[32,2590,880],{"class":155},[32,2592,165],{"class":62},[32,2594,2595,2598,2600,2602],{"class":34,"line":96},[32,2596,2597],{"class":58},"    from",[32,2599,71],{"class":62},[32,2601,59],{"class":58},[32,2603,2604],{"class":62}," Lock\n",[32,2606,2607,2610,2612,2615,2618,2620,2623,2626],{"class":34,"line":110},[32,2608,2609],{"class":62},"    counter ",[32,2611,174],{"class":58},[32,2613,2614],{"class":62}," Value(",[32,2616,2617],{"class":155},"\"i\"",[32,2619,143],{"class":62},[32,2621,2622],{"class":134},"0",[32,2624,2625],{"class":62},")          ",[32,2627,2628],{"class":38},"# 'i' = C int, allocated in SHARED memory across processes\n",[32,2630,2631,2634,2636],{"class":34,"line":115},[32,2632,2633],{"class":62},"    lock ",[32,2635,174],{"class":58},[32,2637,2638],{"class":62}," Lock()\n",[32,2640,2641,2644,2646,2649,2651,2653,2656,2658,2660,2663,2665,2667,2669,2671,2673,2675],{"class":34,"line":121},[32,2642,2643],{"class":62},"    processes ",[32,2645,174],{"class":58},[32,2647,2648],{"class":62}," [Process(",[32,2650,1117],{"class":180},[32,2652,174],{"class":58},[32,2654,2655],{"class":62},"worker, ",[32,2657,1125],{"class":180},[32,2659,174],{"class":58},[32,2661,2662],{"class":62},"(counter, lock)) ",[32,2664,1138],{"class":58},[32,2666,999],{"class":62},[32,2668,277],{"class":58},[32,2670,511],{"class":134},[32,2672,384],{"class":62},[32,2674,339],{"class":134},[32,2676,1151],{"class":62},[32,2678,2679,2681,2684,2686],{"class":34,"line":152},[32,2680,271],{"class":58},[32,2682,2683],{"class":62}," p ",[32,2685,277],{"class":58},[32,2687,2688],{"class":62}," processes:\n",[32,2690,2691],{"class":34,"line":159},[32,2692,2693],{"class":62},"        p.start()\n",[32,2695,2696,2698,2700,2702],{"class":34,"line":168},[32,2697,271],{"class":58},[32,2699,2683],{"class":62},[32,2701,277],{"class":58},[32,2703,2688],{"class":62},[32,2705,2706],{"class":34,"line":192},[32,2707,2708],{"class":62},"        p.join()\n",[32,2710,2711,2713,2716],{"class":34,"line":201},[32,2712,381],{"class":134},[32,2714,2715],{"class":62},"(counter.value)   ",[32,2717,2718],{"class":38},"# 10 — Value\u002FArray are the explicit mechanisms for sharing primitive state\n",[1465,2720,2721,2724,2725,2728,2729,2731,2732,2735,2736,438,2738,2740],{},[29,2722,2723],{},"Value"," and ",[29,2726,2727],{},"Array"," are ",[29,2730,2369],{},"'s narrow escape hatches for sharing simple, fixed-layout data (a single number, a fixed-size array) across process boundaries via actual shared memory — for anything more complex (dicts, lists, custom objects), use a ",[29,2733,2734],{},"Manager()",", which runs a separate server process brokering access, at higher overhead than ",[29,2737,2723],{},[29,2739,2727],{}," but much more flexible.",[14,2742,2744,2747,2748],{"id":2743},"if-__name__-__main__-not-optional-with-multiprocessing",[29,2745,2746],{},"if __name__ == \"__main__\":"," — Not Optional With ",[29,2749,2369],{},[19,2751,2752],{"language":21},[23,2753,2755],{"className":25,"code":2754,"language":21,"meta":27,"style":27},"# WRONG on Windows and macOS (default 'spawn' start method) — causes infinite recursive process spawning\nfrom multiprocessing import Process\n\ndef worker():\n    print(\"working\")\n\np = Process(target=worker)   # module-level code, NOT guarded\np.start()\n# When spawn re-imports this module in the child process, THIS LINE RUNS AGAIN,\n# spawning another child, which re-imports and spawns another... (RuntimeError, usually caught by a guard)\n",[29,2756,2757,2762,2773,2777,2785,2796,2800,2820,2825,2830],{"__ignoreMap":27},[32,2758,2759],{"class":34,"line":35},[32,2760,2761],{"class":38},"# WRONG on Windows and macOS (default 'spawn' start method) — causes infinite recursive process spawning\n",[32,2763,2764,2766,2768,2770],{"class":34,"line":42},[32,2765,99],{"class":58},[32,2767,71],{"class":62},[32,2769,59],{"class":58},[32,2771,2772],{"class":62}," Process\n",[32,2774,2775],{"class":34,"line":48},[32,2776,52],{"emptyLinePlaceholder":51},[32,2778,2779,2781,2783],{"class":34,"line":55},[32,2780,124],{"class":58},[32,2782,2557],{"class":127},[32,2784,1356],{"class":62},[32,2786,2787,2789,2791,2794],{"class":34,"line":66},[32,2788,381],{"class":134},[32,2790,384],{"class":62},[32,2792,2793],{"class":155},"\"working\"",[32,2795,189],{"class":62},[32,2797,2798],{"class":34,"line":80},[32,2799,52],{"emptyLinePlaceholder":51},[32,2801,2802,2805,2807,2810,2812,2814,2817],{"class":34,"line":88},[32,2803,2804],{"class":62},"p ",[32,2806,174],{"class":58},[32,2808,2809],{"class":62}," Process(",[32,2811,1117],{"class":180},[32,2813,174],{"class":58},[32,2815,2816],{"class":62},"worker)   ",[32,2818,2819],{"class":38},"# module-level code, NOT guarded\n",[32,2821,2822],{"class":34,"line":96},[32,2823,2824],{"class":62},"p.start()\n",[32,2826,2827],{"class":34,"line":110},[32,2828,2829],{"class":38},"# When spawn re-imports this module in the child process, THIS LINE RUNS AGAIN,\n",[32,2831,2832],{"class":34,"line":115},[32,2833,2834],{"class":38},"# spawning another child, which re-imports and spawns another... (RuntimeError, usually caught by a guard)\n",[1465,2836,2837,2838,2841,2842,2845,2846,2848,2849,2852,2853,2856],{},"On the ",[29,2839,2840],{},"spawn"," start method (the default on Windows and macOS since Python 3.8), each new process is a ",[1469,2843,2844],{},"fresh interpreter that re-imports the launching module from scratch"," — any code at module level (not inside ",[29,2847,2746],{},") re-executes in the child, including the ",[29,2850,2851],{},"Process(...).start()"," call itself if it isn't guarded, causing runaway recursive process creation. On Linux, the default ",[29,2854,2855],{},"fork"," start method copies the parent's already-initialized memory instead, which is why this bug is often invisible in Linux-only development and then explodes in CI or on a teammate's Mac.",[19,2858,2859],{"language":21},[23,2860,2862],{"className":25,"code":2861,"language":21,"meta":27,"style":27},"from multiprocessing import Process\n\ndef worker():\n    print(\"working\")\n\nif __name__ == \"__main__\":    # guards module-level process-spawning code — REQUIRED, not just tidy style\n    p = Process(target=worker)\n    p.start()\n    p.join()\n",[29,2863,2864,2874,2878,2886,2896,2900,2916,2932,2937],{"__ignoreMap":27},[32,2865,2866,2868,2870,2872],{"class":34,"line":35},[32,2867,99],{"class":58},[32,2869,71],{"class":62},[32,2871,59],{"class":58},[32,2873,2772],{"class":62},[32,2875,2876],{"class":34,"line":42},[32,2877,52],{"emptyLinePlaceholder":51},[32,2879,2880,2882,2884],{"class":34,"line":48},[32,2881,124],{"class":58},[32,2883,2557],{"class":127},[32,2885,1356],{"class":62},[32,2887,2888,2890,2892,2894],{"class":34,"line":55},[32,2889,381],{"class":134},[32,2891,384],{"class":62},[32,2893,2793],{"class":155},[32,2895,189],{"class":62},[32,2897,2898],{"class":34,"line":66},[32,2899,52],{"emptyLinePlaceholder":51},[32,2901,2902,2904,2906,2908,2910,2913],{"class":34,"line":80},[32,2903,871],{"class":58},[32,2905,874],{"class":134},[32,2907,877],{"class":58},[32,2909,880],{"class":155},[32,2911,2912],{"class":62},":    ",[32,2914,2915],{"class":38},"# guards module-level process-spawning code — REQUIRED, not just tidy style\n",[32,2917,2918,2921,2923,2925,2927,2929],{"class":34,"line":88},[32,2919,2920],{"class":62},"    p ",[32,2922,174],{"class":58},[32,2924,2809],{"class":62},[32,2926,1117],{"class":180},[32,2928,174],{"class":58},[32,2930,2931],{"class":62},"worker)\n",[32,2933,2934],{"class":34,"line":96},[32,2935,2936],{"class":62},"    p.start()\n",[32,2938,2939],{"class":34,"line":110},[32,2940,2941],{"class":62},"    p.join()\n",[14,2943,2945,2948],{"id":2944},"processpoolexecutor-the-high-level-multiprocessing-api",[29,2946,2947],{},"ProcessPoolExecutor"," — The High-Level Multiprocessing API",[19,2950,2951],{"language":21},[23,2952,2954],{"className":25,"code":2953,"language":21,"meta":27,"style":27},"from concurrent.futures import ProcessPoolExecutor\n\ndef is_prime(n):\n    if n \u003C 2:\n        return False\n    return all(n % i for i in range(2, int(n ** 0.5) + 1))\n\nif __name__ == \"__main__\":\n    numbers = list(range(100_000, 100_020))\n    with ProcessPoolExecutor(max_workers=4) as executor:\n        results = list(executor.map(is_prime, numbers))\n    print(list(zip(numbers, results)))\n",[29,2955,2956,2967,2971,2980,2996,3003,3053,3057,3069,3095,3113,3124],{"__ignoreMap":27},[32,2957,2958,2960,2962,2964],{"class":34,"line":35},[32,2959,99],{"class":58},[32,2961,102],{"class":62},[32,2963,59],{"class":58},[32,2965,2966],{"class":62}," ProcessPoolExecutor\n",[32,2968,2969],{"class":34,"line":42},[32,2970,52],{"emptyLinePlaceholder":51},[32,2972,2973,2975,2978],{"class":34,"line":48},[32,2974,124],{"class":58},[32,2976,2977],{"class":127}," is_prime",[32,2979,514],{"class":62},[32,2981,2982,2985,2988,2991,2994],{"class":34,"line":55},[32,2983,2984],{"class":58},"    if",[32,2986,2987],{"class":62}," n ",[32,2989,2990],{"class":58},"\u003C",[32,2992,2993],{"class":134}," 2",[32,2995,165],{"class":62},[32,2997,2998,3000],{"class":34,"line":66},[32,2999,195],{"class":58},[32,3001,3002],{"class":134}," False\n",[32,3004,3005,3007,3010,3013,3016,3018,3020,3022,3024,3026,3028,3031,3033,3035,3037,3040,3043,3045,3048,3050],{"class":34,"line":80},[32,3006,537],{"class":58},[32,3008,3009],{"class":134}," all",[32,3011,3012],{"class":62},"(n ",[32,3014,3015],{"class":58},"%",[32,3017,506],{"class":62},[32,3019,1138],{"class":58},[32,3021,506],{"class":62},[32,3023,277],{"class":58},[32,3025,511],{"class":134},[32,3027,384],{"class":62},[32,3029,3030],{"class":134},"2",[32,3032,143],{"class":62},[32,3034,146],{"class":134},[32,3036,3012],{"class":62},[32,3038,3039],{"class":58},"**",[32,3041,3042],{"class":134}," 0.5",[32,3044,342],{"class":62},[32,3046,3047],{"class":58},"+",[32,3049,1015],{"class":134},[32,3051,3052],{"class":62},"))\n",[32,3054,3055],{"class":34,"line":88},[32,3056,52],{"emptyLinePlaceholder":51},[32,3058,3059,3061,3063,3065,3067],{"class":34,"line":96},[32,3060,871],{"class":58},[32,3062,874],{"class":134},[32,3064,877],{"class":58},[32,3066,880],{"class":155},[32,3068,165],{"class":62},[32,3070,3071,3074,3076,3079,3081,3084,3086,3088,3090,3093],{"class":34,"line":110},[32,3072,3073],{"class":62},"    numbers ",[32,3075,174],{"class":58},[32,3077,3078],{"class":134}," list",[32,3080,384],{"class":62},[32,3082,3083],{"class":134},"range",[32,3085,384],{"class":62},[32,3087,1132],{"class":134},[32,3089,143],{"class":62},[32,3091,3092],{"class":134},"100_020",[32,3094,3052],{"class":62},[32,3096,3097,3099,3101,3103,3105,3107,3109,3111],{"class":34,"line":115},[32,3098,328],{"class":58},[32,3100,720],{"class":62},[32,3102,334],{"class":180},[32,3104,174],{"class":58},[32,3106,910],{"class":134},[32,3108,342],{"class":62},[32,3110,74],{"class":58},[32,3112,2207],{"class":62},[32,3114,3115,3117,3119,3121],{"class":34,"line":121},[32,3116,2487],{"class":62},[32,3118,174],{"class":58},[32,3120,3078],{"class":134},[32,3122,3123],{"class":62},"(executor.map(is_prime, numbers))\n",[32,3125,3126,3128,3130,3133,3135,3138],{"class":34,"line":152},[32,3127,381],{"class":134},[32,3129,384],{"class":62},[32,3131,3132],{"class":134},"list",[32,3134,384],{"class":62},[32,3136,3137],{"class":134},"zip",[32,3139,3140],{"class":62},"(numbers, results)))\n",[1465,3142,3143,3145,3146,438,3149,438,3152,3155,3156,3158],{},[29,3144,2947],{}," shares the exact same ",[29,3147,3148],{},".submit()",[29,3150,3151],{},".map()",[29,3153,3154],{},"as_completed()"," API as ",[29,3157,2346],{}," — swapping between thread-based and process-based parallelism for a given workload is often a one-line change, which makes it easy to benchmark both and pick whichever actually performs better for the workload at hand.",[14,3160,3162],{"id":3161},"choosing-threading-vs-multiprocessing-vs-async","Choosing Threading vs Multiprocessing vs Async",[3164,3165,3166,3182],"table",{},[3167,3168,3169],"thead",{},[3170,3171,3172,3176,3179],"tr",{},[3173,3174,3175],"th",{},"Workload",[3173,3177,3178],{},"Best tool",[3173,3180,3181],{},"Why",[3183,3184,3185,3202,3214,3226],"tbody",{},[3170,3186,3187,3191,3199],{},[3188,3189,3190],"td",{},"I\u002FO-bound (network, disk, waiting)",[3188,3192,3193,3195,3196],{},[29,3194,1317],{}," or ",[29,3197,3198],{},"asyncio",[3188,3200,3201],{},"GIL is released during I\u002FO waits; no need for separate processes",[3170,3203,3204,3207,3211],{},[3188,3205,3206],{},"CPU-bound (heavy computation)",[3188,3208,3209],{},[29,3210,2369],{},[3188,3212,3213],{},"Bypasses the GIL entirely via separate interpreters\u002Fprocesses",[3170,3215,3216,3219,3223],{},[3188,3217,3218],{},"Thousands of concurrent I\u002FO tasks",[3188,3220,3221],{},[29,3222,3198],{},[3188,3224,3225],{},"Threads have real memory\u002FOS overhead per thread; coroutines are far cheaper (chapter 22)",[3170,3227,3228,3231,3236],{},[3188,3229,3230],{},"Mixed \u002F simplicity over raw throughput",[3188,3232,3233,3235],{},[29,3234,2133],{}," (either pool)",[3188,3237,3238],{},"Uniform, simple API; easy to swap thread pool for process pool",[14,3240,3242],{"id":3241},"tips-tricks","💡 Tips & Tricks",[3244,3245,3246,3256,3273,3294,3306],"ul",{},[3247,3248,3249,3252,3253,3255],"li",{},[1469,3250,3251],{},"Performance",": profile before reaching for ",[29,3254,2369],{}," — process creation and pickling data across the boundary has real overhead, and for small workloads a naive multiprocessing version can be slower than a single-threaded one; it pays off on genuinely CPU-heavy, easily-partitioned work.",[3247,3257,3258,3261,3262,3264,3265,3268,3269,3272],{},[1469,3259,3260],{},"Debug",": a hanging program that never exits is often a ",[29,3263,1479],{}," acquired but never released on an exception path, or a non-daemon thread that was never ",[29,3266,3267],{},".join()","-ed — use ",[29,3270,3271],{},"threading.enumerate()"," to list all currently alive threads when diagnosing a hang.",[3247,3274,3275,3278,3279,3281,3282,438,3284,3286,3287,438,3290,3293],{},[1469,3276,3277],{},"Idiom",": prefer ",[29,3280,2133],{}," (",[29,3283,2346],{},[29,3285,2947],{},") over raw ",[29,3288,3289],{},"threading.Thread",[29,3291,3292],{},"multiprocessing.Process"," for anything beyond a single one-off background task — the pool API handles queuing, result collection, and exception propagation for you.",[3247,3295,3296,3299,3300,3302,3303,3305],{},[1469,3297,3298],{},"Safety",": never share a mutable object across threads without a ",[29,3301,1479],{}," (or use ",[29,3304,1933],{},", which is internally safe) — \"it worked in my testing\" is not evidence of thread safety, since race conditions are often timing-dependent and can pass thousands of test runs before failing in production under different load.",[3247,3307,3308,3310,3311,3314,3315,3318],{},[1469,3309,3277],{},": set ",[29,3312,3313],{},"daemon=True"," on background threads that should not prevent the program from exiting (e.g., a periodic heartbeat) — a non-daemon thread that's still running keeps the entire Python process alive even after ",[29,3316,3317],{},"main()"," returns.",[14,3320,3322],{"id":3321},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[3244,3324,3325,3337,3348,3363,3380],{},[3247,3326,3327,3333,3334,3336],{},[1469,3328,3329,3330,3332],{},"The GIL means ",[29,3331,1317],{}," provides no real parallelism for CPU-bound Python code"," — adding more threads to a compute-heavy loop can even be slower than sequential execution, due to the overhead of the GIL being repeatedly released and reacquired between threads; use ",[29,3335,2369],{}," for CPU-bound parallelism instead.",[3247,3338,3339,3344,3345,3347],{},[1469,3340,3341,3343],{},[29,3342,1475],{}," is not atomic",", even though it looks like a single operation — it's a read, an add, and a write, and the GIL can switch threads between any of those steps, producing lost updates under concurrent access without a ",[29,3346,1479],{},".",[3247,3349,3350,3359,3360,3362],{},[1469,3351,3352,3353,3355,3356,3358],{},"On Windows and macOS, ",[29,3354,2369],{}," code not guarded by ",[29,3357,2746],{}," can spawn processes recursively without bound",", because the ",[29,3361,2840],{}," start method re-imports the launching module fresh in every child process, re-running any unguarded module-level code including the process-creation call itself.",[3247,3364,3365,3375,3376,3379],{},[1469,3366,3367,3368,3195,3371,3374],{},"Data passed to a ",[29,3369,3370],{},"multiprocessing.Pool",[29,3372,3373],{},"Process"," must be picklable"," — lambdas, open file handles, database connections, and many other objects can't cross the process boundary and raise ",[29,3377,3378],{},"PicklingError"," at the point of submission, not always where the developer expects.",[3247,3381,3382,3399],{},[1469,3383,3384,3385,3387,3388,3391,3392,3395,3396,3398],{},"A ",[29,3386,1479],{}," acquired in a ",[29,3389,3390],{},"try"," block without ",[29,3393,3394],{},"finally"," (or, better, without using ",[29,3397,1641],{},") that is never released on an exception path causes every other thread waiting on that same lock to block forever"," — a silent deadlock with no traceback, since the waiting threads are simply parked, not crashed.",[14,3401,3403],{"id":3402},"spot-the-bug","🧠 Spot the Bug",[1465,3405,3406],{},"A script fans out ten \"download and process\" tasks across threads and accumulates results into a shared list. It occasionally produces fewer than ten results, with no exception raised. Find the bug.",[19,3408,3409],{"language":21},[23,3410,3412],{"className":25,"code":3411,"language":21,"meta":27,"style":27},"import threading\n\nresults = []\n\ndef fetch_and_store(item_id):\n    data = f\"result-{item_id}\"\n    if len(results) \u003C 10:\n        results.append(data)\n\nthreads = [threading.Thread(target=fetch_and_store, args=(i,)) for i in range(10)]\nfor t in threads:\n    t.start()\nfor t in threads:\n    t.join()\n\nprint(len(results))   # sometimes 10, sometimes less\n",[29,3413,3414,3420,3424,3434,3438,3448,3471,3488,3493,3497,3533,3543,3547,3557,3561,3565],{"__ignoreMap":27},[32,3415,3416,3418],{"class":34,"line":35},[32,3417,59],{"class":58},[32,3419,63],{"class":62},[32,3421,3422],{"class":34,"line":42},[32,3423,52],{"emptyLinePlaceholder":51},[32,3425,3426,3429,3431],{"class":34,"line":48},[32,3427,3428],{"class":62},"results ",[32,3430,174],{"class":58},[32,3432,3433],{"class":62}," []\n",[32,3435,3436],{"class":34,"line":55},[32,3437,52],{"emptyLinePlaceholder":51},[32,3439,3440,3442,3445],{"class":34,"line":66},[32,3441,124],{"class":58},[32,3443,3444],{"class":127}," fetch_and_store",[32,3446,3447],{"class":62},"(item_id):\n",[32,3449,3450,3453,3455,3458,3461,3463,3466,3468],{"class":34,"line":80},[32,3451,3452],{"class":62},"    data ",[32,3454,174],{"class":58},[32,3456,3457],{"class":58}," f",[32,3459,3460],{"class":155},"\"result-",[32,3462,393],{"class":134},[32,3464,3465],{"class":62},"item_id",[32,3467,402],{"class":134},[32,3469,3470],{"class":155},"\"\n",[32,3472,3473,3475,3478,3481,3483,3486],{"class":34,"line":88},[32,3474,2984],{"class":58},[32,3476,3477],{"class":134}," len",[32,3479,3480],{"class":62},"(results) ",[32,3482,2990],{"class":58},[32,3484,3485],{"class":134}," 10",[32,3487,165],{"class":62},[32,3489,3490],{"class":34,"line":96},[32,3491,3492],{"class":62},"        results.append(data)\n",[32,3494,3495],{"class":34,"line":110},[32,3496,52],{"emptyLinePlaceholder":51},[32,3498,3499,3501,3503,3505,3507,3509,3512,3514,3516,3519,3521,3523,3525,3527,3529,3531],{"class":34,"line":115},[32,3500,1109],{"class":62},[32,3502,174],{"class":58},[32,3504,1114],{"class":62},[32,3506,1117],{"class":180},[32,3508,174],{"class":58},[32,3510,3511],{"class":62},"fetch_and_store, ",[32,3513,1125],{"class":180},[32,3515,174],{"class":58},[32,3517,3518],{"class":62},"(i,)) ",[32,3520,1138],{"class":58},[32,3522,506],{"class":62},[32,3524,277],{"class":58},[32,3526,511],{"class":134},[32,3528,384],{"class":62},[32,3530,339],{"class":134},[32,3532,1151],{"class":62},[32,3534,3535,3537,3539,3541],{"class":34,"line":121},[32,3536,1138],{"class":58},[32,3538,1158],{"class":62},[32,3540,277],{"class":58},[32,3542,1433],{"class":62},[32,3544,3545],{"class":34,"line":152},[32,3546,1438],{"class":62},[32,3548,3549,3551,3553,3555],{"class":34,"line":159},[32,3550,1138],{"class":58},[32,3552,1158],{"class":62},[32,3554,277],{"class":58},[32,3556,1433],{"class":62},[32,3558,3559],{"class":34,"line":168},[32,3560,1453],{"class":62},[32,3562,3563],{"class":34,"line":192},[32,3564,52],{"emptyLinePlaceholder":51},[32,3566,3567,3569,3571,3574,3577],{"class":34,"line":201},[32,3568,1179],{"class":134},[32,3570,384],{"class":62},[32,3572,3573],{"class":134},"len",[32,3575,3576],{"class":62},"(results))   ",[32,3578,3579],{"class":38},"# sometimes 10, sometimes less\n",[3581,3582,3583,3587,3628,3631,3839],"details",{},[3584,3585,3586],"summary",{},"Answer",[1465,3588,3589,3592,3593,3596,3597,3600,3601,3604,3605,3608,3609,3611,3612,3616,3617,3619,3620,3623,3624,3627],{},[29,3590,3591],{},"list.append()"," itself is thread-safe in CPython (it's a single bytecode-level operation protected by the GIL), but ",[29,3594,3595],{},"if len(results) \u003C 10: results.append(data)"," is ",[1469,3598,3599],{},"two separate operations"," — a length check, then an append — with no atomicity guarantee across the two. Between one thread checking ",[29,3602,3603],{},"len(results) \u003C 10"," and it actually calling ",[29,3606,3607],{},".append()",", other threads can interleave, but that's not even the real bug here (all 10 threads should still append since none of them exceed 10 in this scenario) — the actual issue is subtler: ",[29,3610,3603],{}," is a check-then-act race in general, and in a variation of this pattern with more threads than the limit, some threads pass the check with a stale length, all append, and the list ends up ",[3613,3614,3615],"em",{},"larger"," than intended, or (as observed here) if any exception occurs inside a thread's target function it is silently swallowed by ",[29,3618,3289],{}," — printed to stderr but never re-raised to the main thread — masking a ",[29,3621,3622],{},"fetch_and_store"," failure entirely and undercounting ",[29,3625,3626],{},"results"," with no visible traceback in the main thread's flow.",[1465,3629,3630],{},"The fix is a lock around the read-modify-write of the shared list, and explicit propagation of any exception raised inside a thread:",[19,3632,3633],{"language":21},[23,3634,3636],{"className":25,"code":3635,"language":21,"meta":27,"style":27},"import threading\n\nresults = []\nlock = threading.Lock()\nerrors = []\n\ndef fetch_and_store(item_id):\n    try:\n        data = f\"result-{item_id}\"\n        with lock:\n            results.append(data)\n    except Exception as exc:\n        errors.append(exc)\n\nthreads = [threading.Thread(target=fetch_and_store, args=(i,)) for i in range(10)]\nfor t in threads:\n    t.start()\nfor t in threads:\n    t.join()\n\nif errors:\n    raise errors[0]\nprint(len(results))   # reliably 10\n",[29,3637,3638,3644,3648,3656,3664,3673,3677,3685,3691,3710,3716,3721,3731,3736,3740,3774,3784,3788,3798,3802,3806,3813,3826],{"__ignoreMap":27},[32,3639,3640,3642],{"class":34,"line":35},[32,3641,59],{"class":58},[32,3643,63],{"class":62},[32,3645,3646],{"class":34,"line":42},[32,3647,52],{"emptyLinePlaceholder":51},[32,3649,3650,3652,3654],{"class":34,"line":48},[32,3651,3428],{"class":62},[32,3653,174],{"class":58},[32,3655,3433],{"class":62},[32,3657,3658,3660,3662],{"class":34,"line":55},[32,3659,957],{"class":62},[32,3661,174],{"class":58},[32,3663,962],{"class":62},[32,3665,3666,3669,3671],{"class":34,"line":66},[32,3667,3668],{"class":62},"errors ",[32,3670,174],{"class":58},[32,3672,3433],{"class":62},[32,3674,3675],{"class":34,"line":80},[32,3676,52],{"emptyLinePlaceholder":51},[32,3678,3679,3681,3683],{"class":34,"line":88},[32,3680,124],{"class":58},[32,3682,3444],{"class":127},[32,3684,3447],{"class":62},[32,3686,3687,3689],{"class":34,"line":96},[32,3688,162],{"class":58},[32,3690,165],{"class":62},[32,3692,3693,3696,3698,3700,3702,3704,3706,3708],{"class":34,"line":110},[32,3694,3695],{"class":62},"        data ",[32,3697,174],{"class":58},[32,3699,3457],{"class":58},[32,3701,3460],{"class":155},[32,3703,393],{"class":134},[32,3705,3465],{"class":62},[32,3707,402],{"class":134},[32,3709,3470],{"class":155},[32,3711,3712,3714],{"class":34,"line":115},[32,3713,1069],{"class":58},[32,3715,2567],{"class":62},[32,3717,3718],{"class":34,"line":121},[32,3719,3720],{"class":62},"            results.append(data)\n",[32,3722,3723,3725,3727,3729],{"class":34,"line":152},[32,3724,204],{"class":58},[32,3726,207],{"class":134},[32,3728,210],{"class":58},[32,3730,2308],{"class":62},[32,3732,3733],{"class":34,"line":159},[32,3734,3735],{"class":62},"        errors.append(exc)\n",[32,3737,3738],{"class":34,"line":168},[32,3739,52],{"emptyLinePlaceholder":51},[32,3741,3742,3744,3746,3748,3750,3752,3754,3756,3758,3760,3762,3764,3766,3768,3770,3772],{"class":34,"line":192},[32,3743,1109],{"class":62},[32,3745,174],{"class":58},[32,3747,1114],{"class":62},[32,3749,1117],{"class":180},[32,3751,174],{"class":58},[32,3753,3511],{"class":62},[32,3755,1125],{"class":180},[32,3757,174],{"class":58},[32,3759,3518],{"class":62},[32,3761,1138],{"class":58},[32,3763,506],{"class":62},[32,3765,277],{"class":58},[32,3767,511],{"class":134},[32,3769,384],{"class":62},[32,3771,339],{"class":134},[32,3773,1151],{"class":62},[32,3775,3776,3778,3780,3782],{"class":34,"line":201},[32,3777,1138],{"class":58},[32,3779,1158],{"class":62},[32,3781,277],{"class":58},[32,3783,1433],{"class":62},[32,3785,3786],{"class":34,"line":216},[32,3787,1438],{"class":62},[32,3789,3790,3792,3794,3796],{"class":34,"line":230},[32,3791,1138],{"class":58},[32,3793,1158],{"class":62},[32,3795,277],{"class":58},[32,3797,1433],{"class":62},[32,3799,3800],{"class":34,"line":235},[32,3801,1453],{"class":62},[32,3803,3804],{"class":34,"line":251},[32,3805,52],{"emptyLinePlaceholder":51},[32,3807,3808,3810],{"class":34,"line":257},[32,3809,871],{"class":58},[32,3811,3812],{"class":62}," errors:\n",[32,3814,3815,3818,3821,3823],{"class":34,"line":268},[32,3816,3817],{"class":58},"    raise",[32,3819,3820],{"class":62}," errors[",[32,3822,2622],{"class":134},[32,3824,3825],{"class":62},"]\n",[32,3827,3828,3830,3832,3834,3836],{"class":34,"line":283},[32,3829,1179],{"class":134},[32,3831,384],{"class":62},[32,3833,3573],{"class":134},[32,3835,3576],{"class":62},[32,3837,3838],{"class":38},"# reliably 10\n",[1465,3840,3841,1472,3844,3846,3847,3849,3850,3852],{},[1469,3842,3843],{},"The lesson",[29,3845,3289],{}," swallows exceptions raised inside the target function (printing a traceback to stderr but not stopping the main thread or ",[29,3848,3267],{},"), and any check-then-act sequence on shared mutable state needs a ",[29,3851,1479],{}," around the whole sequence, not just the final mutation — always assume interleaving can happen between any two lines touching shared state.",[14,3854,3856],{"id":3855},"key-takeaways","Key Takeaways",[3244,3858,3859,3865,3870,3879,3890,3903],{},[3247,3860,3861,3862,3864],{},"The GIL allows only one thread to execute Python bytecode at a time in CPython, which means ",[29,3863,1317],{}," does not provide real parallelism for CPU-bound work — it's only helpful for I\u002FO-bound work, where the GIL is released during waits.",[3247,3866,3867,3869],{},[29,3868,2369],{}," sidesteps the GIL by using separate OS processes with their own interpreters, at the cost of pickling overhead for data crossing the process boundary and no default shared memory.",[3247,3871,3872,3873,3875,3876,3878],{},"Shared mutable state accessed from multiple threads needs an explicit ",[29,3874,1479],{}," around every read-modify-write sequence — operations that look atomic (",[29,3877,1475],{},", \"check then append\") frequently are not.",[3247,3880,3881,3883,3884,3886,3887,3889],{},[29,3882,2746],{}," is mandatory, not stylistic, around ",[29,3885,2369],{}," code on Windows\u002FmacOS — the ",[29,3888,2840],{}," start method re-imports the module fresh in each child process.",[3247,3891,3892,438,3895,3897,3898,438,3900,3902],{},[29,3893,3894],{},"concurrent.futures.ThreadPoolExecutor",[29,3896,2947],{}," provide a uniform, higher-level pool API that's usually preferable to hand-managing raw ",[29,3899,2362],{},[29,3901,3373],{}," objects, and make it easy to switch between the two.",[3247,3904,3905,3906,3908,3909,3911,3912,3915],{},"Exceptions raised inside a ",[29,3907,3289],{}," target are not automatically propagated to the main thread — they must be explicitly caught and communicated back (e.g., via a shared list, ",[29,3910,1933],{},", or ",[29,3913,3914],{},"concurrent.futures.Future.result()",", which does re-raise).",[3917,3918,3919],"style",{},"html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}html pre.shiki code .sCrzJ, html code.shiki .sCrzJ{--shiki-default:#E36209;--shiki-github-dark:#FFAB70}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html.github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}",{"title":27,"searchDepth":42,"depth":42,"links":3921},[3922,3923,3928,3930,3932,3934,3936,3938,3939,3940,3941,3942],{"id":16,"depth":42,"text":17},{"id":1314,"depth":42,"text":3924,"children":3925},"threading — Shared Memory, Explicit Locks",[3926],{"id":1657,"depth":48,"text":3927},"RLock, Condition, and Event",{"id":1930,"depth":42,"text":3929},"queue.Queue — Thread-Safe Producer\u002FConsumer",{"id":2130,"depth":42,"text":3931},"concurrent.futures — The High-Level Pool API",{"id":2366,"depth":42,"text":3933},"multiprocessing — True Parallelism via Separate Processes",{"id":2743,"depth":42,"text":3935},"if __name__ == \"__main__\": — Not Optional With multiprocessing",{"id":2944,"depth":42,"text":3937},"ProcessPoolExecutor — The High-Level Multiprocessing API",{"id":3161,"depth":42,"text":3162},{"id":3241,"depth":42,"text":3242},{"id":3321,"depth":42,"text":3322},{"id":3402,"depth":42,"text":3403},{"id":3855,"depth":42,"text":3856},"md",{},"\u002Fpython\u002F21-concurrency-threading-and-multiprocessing",{"title":5,"description":27},"python\u002F21-concurrency-threading-and-multiprocessing","TtcKF7TBZUek9y-AkFjSPnPHrA13_rg0bXv4npXpElI",1789924651685]