concurrent.interpreters — Flera tolkar i samma process

Tillagd i version 3.14.

Källkod: Lib/concurrent/interpreters


Modulen concurrent.interpreters konstruerar gränssnitt på högre nivå ovanpå modulen _interpreters på lägre nivå.

Modulen är främst avsedd att tillhandahålla ett grundläggande API för att hantera tolkar (även kallade ”undertolkar”) och köra saker i dem. Att köra saker innebär oftast att man växlar till en tolk (i den aktuella tråden) och anropar en funktion i det exekveringssammanhanget.

För samtidighet tillhandahåller tolkarna själva (och den här modulen) inte mycket mer än isolering, vilket i sig inte är användbart. Faktisk samtidighet är tillgänglig separat genom threads Se under

Se även

InterpreterPoolExecutor

kombinerar trådar med tolkar i ett välbekant gränssnitt.

Isolering av tilläggsmoduler

så här uppdaterar du en tilläggsmodul för att stödja flera tolkar

PEP 554

PEP 734

PEP 684

Tillgänglighet: not WASI.

Den här modulen fungerar inte eller är inte tillgänglig på WebAssembly. Se WebAssembly-plattformar för mer information.

Information om nyckel

Innan vi går vidare finns det ett antal detaljer att tänka på när det gäller att använda flera tolkar:

  • isolated, som standard

  • inga implicita trådar

  • inte alla PyPI-paket stöder användning i flera tolkar ännu

Introduktion

En ”tolk” är i själva verket exekveringskontexten för Pythons runtime. Den innehåller allt tillstånd som körtiden behöver för att exekvera ett program. Detta inkluderar saker som importstatus och builtins. (Varje tråd, även om det bara finns en huvudtråd, har lite extra körtidstillstånd, utöver den aktuella tolken, relaterat till det aktuella undantaget och bytecode eval-loopen)

Konceptet och funktionaliteten för tolken har varit en del av Python sedan version 2.2, men funktionen var endast tillgänglig via C-API och inte särskilt välkänd, och isoleringen var relativt ofullständig fram till version 3.12.

Flera tolkar och isolering

En Python-implementation kan stödja användning av flera tolkar i samma process. CPython har detta stöd. Varje tolk är effektivt isolerad från de andra (med ett begränsat antal noggrant hanterade processglobala undantag från regeln).

Denna isolering är främst användbar som en stark separation mellan distinkta logiska komponenter i ett program, där du vill ha noggrann kontroll över hur dessa komponenter interagerar.

Anteckning

Tolkar i samma process kan tekniskt sett aldrig vara strikt isolerade från varandra eftersom det finns få restriktioner för minnesåtkomst inom samma process. Pythons runtime gör sitt bästa för att isolera, men tilläggsmoduler kan lätt bryta mot detta. Använd därför inte flera tolkar i säkerhetskänsliga situationer, där de inte bör ha tillgång till varandras data.

Att köra in en tolk

Att köra i en annan tolk innebär att man växlar till den i den aktuella tråden och sedan anropar någon funktion. Runtime kommer att exekvera funktionen med hjälp av den aktuella tolkens tillstånd. Modulen concurrent.interpreters tillhandahåller ett grundläggande API för att skapa och hantera tolkar, samt för att växla och anropa.

Inga andra trådar startas automatiskt för operationen. Det finns dock en hjälpare för det. Det finns en annan dedikerad hjälp för att anropa den inbyggda exec() i en tolk.

När exec() (eller eval()) anropas i en tolk, körs de med tolkens __main__-modul som ”globals”-namnrymd. Detsamma gäller för funktioner som inte är associerade med någon modul. Detta är samma sak som att skript som anropas från kommandoraden körs i modulen __main__.

Samtidighet och parallellism

Som tidigare nämnts tillhandahåller tolkar inte någon samtidighet i sig själva. De representerar strikt den isolerade exekveringskontext som körtiden kommer att använda * i den aktuella tråden*. Denna isolering gör att de liknar processer, men de har fortfarande effektivitet i processen, precis som trådar.

Allt detta sagt, tolkar stöder naturligtvis vissa varianter av samtidighet. Det finns en kraftfull bieffekt av den isoleringen. Det möjliggör ett annat tillvägagångssätt för samtidighet än du kan ta med async eller trådar. Det är en liknande samtidighetsmodell som CSP eller aktormodellen, en modell som är relativt lätt att resonera om.

Du kan dra nytta av den här samtidighetsmodellen i en enda tråd genom att växla fram och tillbaka mellan tolkarna på samma sätt som Stackless. Den här modellen är dock mer användbar när du kombinerar tolkar med flera trådar. Detta innebär oftast att man startar en ny tråd, där man växlar till en annan tolk och kör det man vill där.

Varje faktisk tråd i Python, även om du bara kör i huvudtråden, har sin egen aktuella exekveringskontext. Flera trådar kan använda samma tolk eller olika.

På en hög nivå kan man tänka sig kombinationen av trådar och tolkar som trådar med opt-in-delning.

Som en betydande bonus är tolkarna tillräckligt isolerade för att de inte delar GIL, vilket innebär att kombinera trådar med flera tolkar möjliggör full parallellism med flera kärnor. (Detta har varit fallet sedan Python 3.12.)

Kommunikation mellan tolkar

I praktiken är flera tolkar bara användbara om vi har ett sätt att kommunicera mellan dem. Detta innebär vanligtvis någon form av meddelandepassning, men kan även innebära att data delas på något noggrant hanterat sätt.

Med detta i åtanke tillhandahåller modulen concurrent.interpreters en implementation av queue.Queue, tillgänglig via create_queue().

”Delning” av objekt

Alla data som faktiskt delas mellan tolkar förlorar den trådsäkerhet som tillhandahålls av GIL. Det finns olika alternativ för att hantera detta i tilläggsmoduler. Från Python-kod innebär dock bristen på trådsäkerhet att objekt faktiskt inte kan delas, med några få undantag. Istället måste en kopia skapas, vilket innebär att föränderliga objekt inte förblir synkroniserade.

Som standard kopieras de flesta objekt med pickle när de skickas till en annan tolk. Nästan alla oföränderliga inbyggda objekt delas antingen direkt eller kopieras på ett effektivt sätt. Till exempel:

Det finns ett litet antal Python-typer som faktiskt delar föränderliga data mellan tolkare:

Referens

Denna modul definierar följande funktioner:

concurrent.interpreters.list_all()

Returnerar en list av Interpreter-objekt, ett för varje befintlig tolk.

concurrent.interpreters.get_current()

Returnerar ett Interpreter-objekt för den tolk som körs för tillfället.

concurrent.interpreters.get_main()

Returnerar ett Interpreter-objekt för huvudtolken. Detta är den tolk som körtiden skapade för att köra REPL eller det skript som gavs på kommandoraden. Det är vanligtvis den enda.

concurrent.interpreters.create()

Initiera en ny (inaktiv) Python-tolk och returnera ett Interpreter-objekt för den.

concurrent.interpreters.create_queue()

Initiera en ny tolköverskridande kö och returnera ett Queue-objekt för den.

Tolkningsobjekt

class concurrent.interpreters.Interpreter(id)

En enda tolk i den aktuella processen.

Generellt sett bör inte Interpreter anropas direkt. Använd istället create() eller någon av de andra modulfunktionerna.

id

(skrivskyddad)

Den underliggande tolkens ID.

whence

(skrivskyddad)

En sträng som beskriver varifrån tolken kommer.

is_running()

Returnerar True om tolken för närvarande exekverar kod i sin __main__-modul och False i annat fall.

close()

Slutför och förstör tolken.

prepare_main(ns=None, **kwargs)

Binda objekt i tolkens __main__-modul.

Vissa objekt delas faktiskt och vissa kopieras på ett effektivt sätt, men de flesta kopieras via pickle. Se ”Delning” av objekt.

exec(code, /, dedent=True)

Kör den angivna källkoden i tolken (i den aktuella tråden).

call(callable, /, *args, **kwargs)

Returnerar resultatet av att anropa och köra den angivna funktionen i tolken (i den aktuella tråden).

call_in_thread(callable, /, *args, **kwargs)

Kör den angivna funktionen i tolken (i en ny tråd).

Undantag

exception concurrent.interpreters.InterpreterError

Detta undantag, som är en underklass till Exception, uppstår när ett tolkningsrelaterat fel inträffar.

exception concurrent.interpreters.InterpreterNotFoundError

Detta undantag, som är en underklass till InterpreterError, uppstår när den avsedda tolken inte längre finns.

exception concurrent.interpreters.ExecutionFailed

Detta undantag, som är en underklass till InterpreterError, uppstår när den löpande koden har orsakat ett undantag som inte fångats upp.

excinfo

En enkel ögonblicksbild av det undantag som uppstod i den andra tolken.

exception concurrent.interpreters.NotShareableError

Detta undantag, som är en underklass till TypeError, uppstår när ett objekt inte kan skickas till en annan tolk.

Kommunikation mellan tolkar

class concurrent.interpreters.Queue(id)

En wrapper runt en lågnivå, tolkningsöverskridande kö, som implementerar queue.Queue-gränssnittet. Den underliggande kön kan endast skapas genom create_queue().

Vissa objekt delas faktiskt och vissa kopieras på ett effektivt sätt, men de flesta kopieras via pickle. Se ”Delning” av objekt.

id

(skrivskyddad)

Köns ID.

exception concurrent.interpreters.QueueEmptyError

Detta undantag, en subklass av queue.Empty, uppstår från Queue.get() och Queue.get_nowait() när kön är tom.

exception concurrent.interpreters.QueueFullError

Detta undantag, en subklass av queue.Full, uppstår från Queue.put() och Queue.put_nowait() när kön är full.

Grundläggande användning

Skapa en tolk och köra kod i den:

från samtidiga importtolkar

interp = tolkar.skapa()

# Kör i den aktuella OS-tråden.

interp.exec('print("spam!")')

interp.exec("""if True:
    print('spam!')
    """)

från textwrap import dedent
interp.exec(dedent("""
    print('skräppost!')
    """))

def run(arg):
    returnerar arg

res = interp.call(run, 'spam!')
print(res)

def run():
    print('skräppost!')

interp.call(run)

# Kör i en ny OS-tråd.

t = interp.call_in_thread(run)
t.join()