Larijs Hastings 16. maijā publicēja PyPI pakotni blanket 1.0. Tā daudzpavedienu Python testos atņem izpildes kārtību operētājsistēmas plānotājam un nodod to pašam testam. Bibliotēkas kodols ir viens fails ar 8352 rindām. Testu fails ir vēl garāks: 9799 rindas.
Sacensība starp diviem pavedieniem nostrādā varbūt vienu reizi simtā palaišanu. Tests, kas to noķer, nav atkārtojams. Kļūdu apstrādes zars, kas iedarbojas tikai sacensības brīdī, testu pārklājumā paliek balts. Bezgila Python šo problēmu padara redzamāku: 3.13 laidienā tas bija eksperimentāls, 3.14 to atbalsta oficiāli ar 5 līdz 10 % lielāku patēriņu, 3.15 pievieno stabilo ABI. Kods, ko GIL agrāk sakārtoja rindā, tagad tiešām iet paralēli.
Kā tests kļūst par plānotāju
Testā izveido Scenario objektu. No tā ņem visu pārējo: scenario.Lock() dod slēdzeni, scenario.thread() dod pavedienu. Izsaukums scenario.api(lock) atgriež otru rokturi tai pašai slēdzenei. Caur šo rokturi tests dala piekļuvi.
Divas galvenās metodes ir relay() un cycle(). Izsaukums relay(A, B) nosaka, ka slēdzeni vispirms saņem pavediens A, tikai pēc tam B. Metode cycle() to pašu dara barjerām un notikumiem: nosaka pamodināšanas secību. Zem tām ir vidējais slānis. Metode park(pavediens, metode) aptur pavedienu tieši uz izsauktās metodes, skip() metodi izlaiž, finish() ļauj pavedienam aiziet līdz galam. Vairāku pavedienu orķestrēšanai ir objekti Driver, Chain un Dispatch.
Vienkāršākais tests sākas ar scenario = blanket.Scenario() un lock = scenario.Lock(). Tad nāk divi pavedieni no scenario.thread(worker, 'A') un scenario.thread(worker, 'B'), pēc tam with scenario: list(lock_api.relay(A, B)). Metode relay() atgriež ģeneratoru, tāpēc tests to izsmeļ ar list(). Bez šī izsaukuma pavedieni nekur netiek.
Viszemākais slānis ir scenario.wait(). Hastings to veidojis pēc Win32 funkcijas WaitForMultipleObjects. Katrs izsaukums uz blanket primitīva kļūst par transakciju ar metodēm unblock(), expire() un disregard().
Blanket atdod kontroli pār izpildes kārtību daudzpavedienu kodā un ļauj rakstīt deterministiskus daudzpavedienu testus.
Tā Hastings savu bibliotēku aprakstīja PyCon US 2026 programmā. Uzstāšanās notika sestdien, 16. maijā, plkst. 10:30 Grand Ballroom B zālē. Pakotne PyPI parādījās tās pašas dienas rītā plkst. 6:15 UTC.
Septiņi primitīvi, pieci stāvokļi
blanket apņem septiņus threading moduļa primitīvus: Lock, RLock, Barrier, Event, Condition, Semaphore un BoundedSemaphore. Katram no tiem ir četri objekti. Pirmais ir rokturis, ko izmanto darba pavediens. Otrais ir API objekts plānotājam. Trešais ir neregulēts rokturis, ko plānotājs lieto savām vajadzībām. Ceturtais glabā iekšējo stāvokli.
Transakcija var stāvēt piecos stāvokļos: BLOCKED pirms operācijas, COMMIT pie taimauta lēmuma, WAITING īstā primitīva iekšienē, STALLED pēc pamodināšanas un PAUSED kā vispārēja aizture. Katrā no tiem pavediens gaida atļauju no testa.
Apakšā strādā īstie threading primitīvi. Izsaukums lock.acquire() uz blanket slēdzenes izsauc threading.Lock.acquire(). Tāpēc isinstance(scenario.Lock(), threading.Lock) atgriež True. Vienīgā pazīme atkļūdošanā ir heksadecimālais identifikators lielajiem burtiem: blanket objektam 0X78C9905B2CF0, parastam 0x78c990475650.
Injektors piespiež sacensību bezslēdzenes kodā
Slēdzenes neaptver visu. Rinda counter += 1 bez slēdzenes arī var pazaudēt atjauninājumu, tikai tur nav neviena izsaukuma, kur pavedienu apturēt. Modulis blanket.injector ar 678 rindām iestarpina baitkodā izsaukumus starp atsevišķiem priekšrakstiem. Location.text() norāda vietu kodā, inject_call() tur ieliek kontrolpunktu. Pēc tam tests var piespiest tieši to secību, kurā abi pavedieni nolasa vecu vērtību.
Otrs paņēmiens ir scenario.inject(module). Šis konteksta pārvaldnieks aizvieto threading importus mērķa modulī. Tas tiek galā ar abām rakstības formām: gan import threading, gan from threading import Lock.
Cita pieeja nekā modeļu pārbaudītājiem
Rīki, kas Python ekosistēmā jau ir, meklē kļūdas ar atkārtošanu. Rokasgrāmata py-free-threading iesaka pytest-run-parallel un pytest-freethreaded, kas visu testu kopu palaiž daudzos pavedienos, kā arī sys.setswitchinterval() izsaukumu, lai GIL atbrīvotos biežāk. Vēl tur ir ieteikums pirms aizdomīgā koda ielikt threading.Barrier, kas paaugstina izredzes, ka reta kļūda nostrādās.
Modeļu pārbaudītāji Loom, Shuttle un CHESS iet tālāk: tie paši izstaigā iespējamās secības un ziņo, kur programma salūst. blanket dara pretējo. Secību uzraksta cilvēks. Bibliotēka garantē, ka tā izpildīsies katru reizi. Bernāts Gābors savā aprakstā to sauc par piemērotāku regresijas testiem: reiz atrastu kļūdu ar blanket var iesaldēt testā uz visiem laikiem. Tajā pašā tekstā viņš piemin 24 gadus vecu datu sacensību scipy.signal modulī.
Repozitorijs kluss no 9. jūnija
Hastings ir CPython izstrādātājs, Argument Clinic autors un 3.4 kā arī 3.5 laidienu pārvaldnieks. Viņa jaunajā bibliotēkā testu koda ir vairāk nekā implementācijas: fails test_primitives.py ar 9799 rindām pārsniedz primitives.py ar tā 8352 rindām. Injektora testi aizņem vēl 31 KB.
blanket prasa Python 3.11 vai jaunāku. PyPI ir divi laidieni: 0.0.1 un 1.0. Licences fails sākas ar rindu par visām paturētajām tiesībām, tālāk seko standarta MIT teksts.
Repozitorijā ir 36 zvaigznes, pieci atzarojumi un trīs atvērtas problēmas. Pēdējais commit tur ir no 9. jūnija. Dokumentācija dzīvo vienā README failā ar 2197 rindām un 88 KB apjomu. LWN par bibliotēku rakstīja 3. septembra nedēļas izdevumā, gandrīz trīs mēnešus pēc pēdējām koda izmaiņām.
Komentāri
Šim rakstam vēl nav komentāru. Esi pirmais, kurš dalās ar savu viedokli.