Multi-Threading
Base.Threads.@threads
— MacroThreads.@threads [schedule] for ... end
Ein Makro, um eine for
-Schleife parallel auszuführen. Der Iterationsraum wird auf grobkörnige Aufgaben verteilt. Diese Richtlinie kann durch das Argument schedule
angegeben werden. Die Ausführung der Schleife wartet auf die Auswertung aller Iterationen.
Siehe auch: @spawn
und pmap
in Distributed
.
Erweiterte Hilfe
Semantik
Sofern nicht stärkere Garantien durch die Scheduling-Option angegeben sind, hat die von dem @threads
-Makro ausgeführte Schleife die folgende Semantik.
Das @threads
-Makro führt den Schleifeninhalt in einer nicht spezifizierten Reihenfolge und potenziell gleichzeitig aus. Es gibt keine genauen Zuordnungen der Aufgaben und der Arbeiter-Threads an. Die Zuordnungen können bei jeder Ausführung unterschiedlich sein. Der Code des Schleifeninhalts (einschließlich aller von ihm transitiv aufgerufenen Codes) darf keine Annahmen über die Verteilung der Iterationen auf Aufgaben oder den Arbeiter-Thread, in dem sie ausgeführt werden, treffen. Der Schleifeninhalt für jede Iteration muss in der Lage sein, unabhängig von anderen Iterationen Fortschritte zu machen und frei von Datenrennen zu sein. Daher können ungültige Synchronisationen zwischen Iterationen zu einem Deadlock führen, während unsynchronisierte Speicherzugriffe zu undefiniertem Verhalten führen können.
Zum Beispiel implizieren die obigen Bedingungen, dass:
- Ein in einer Iteration genommenes Lock muss innerhalb derselben Iteration freigegeben werden.
- Die Kommunikation zwischen Iterationen unter Verwendung blockierender Primitiven wie
Channel
s ist inkorrekt. - Nur an Orten schreiben, die nicht zwischen Iterationen geteilt werden (es sei denn, ein Lock oder eine atomare Operation wird verwendet).
- Sofern nicht der
:static
-Zeitplan verwendet wird, kann der Wert vonthreadid()
selbst innerhalb einer einzigen Iteration variieren. SieheTask Migration
.
Scheduler
Ohne das Scheduler-Argument ist die genaue Planung nicht spezifiziert und variiert zwischen den Julia-Versionen. Derzeit wird :dynamic
verwendet, wenn der Scheduler nicht angegeben ist.
Das schedule
-Argument ist seit Julia 1.5 verfügbar.
:dynamic
(Standard)
Der :dynamic
-Scheduler führt Iterationen dynamisch auf verfügbaren Arbeiter-Threads aus. Die aktuelle Implementierung geht davon aus, dass die Arbeitslast für jede Iteration gleichmäßig verteilt ist. Diese Annahme kann jedoch in Zukunft aufgehoben werden.
Diese Scheduling-Option ist lediglich ein Hinweis auf den zugrunde liegenden Ausführungsmechanismus. Es können jedoch einige Eigenschaften erwartet werden. Die Anzahl der von dem :dynamic
-Scheduler verwendeten Task
s ist durch ein kleines konstantes Vielfaches der Anzahl der verfügbaren Arbeiter-Threads (Threads.threadpoolsize()
). Jede Aufgabe verarbeitet zusammenhängende Bereiche des Iterationsraums. Daher ist @threads :dynamic for x in xs; f(x); end
typischerweise effizienter als @sync for x in xs; @spawn f(x); end
, wenn length(xs)
erheblich größer ist als die Anzahl der Arbeiter-Threads und die Laufzeit von f(x)
relativ kleiner ist als die Kosten für das Starten und Synchronisieren einer Aufgabe (typischerweise weniger als 10 Mikrosekunden).
Die :dynamic
-Option für das schedule
-Argument ist seit Julia 1.8 verfügbar und der Standard.
:greedy
Der :greedy
-Scheduler startet bis zu Threads.threadpoolsize()
Aufgaben, die jeweils gierig an den gegebenen iterierten Werten arbeiten, sobald sie produziert werden. Sobald eine Aufgabe ihre Arbeit beendet, nimmt sie den nächsten Wert aus dem Iterator. Die Arbeit, die von einer einzelnen Aufgabe erledigt wird, erfolgt nicht unbedingt an zusammenhängenden Werten aus dem Iterator. Der gegebene Iterator kann Werte für immer produzieren, es ist nur die Iterator-Schnittstelle erforderlich (kein Indizieren).
Diese Scheduling-Option ist im Allgemeinen eine gute Wahl, wenn die Arbeitslast der einzelnen Iterationen nicht gleichmäßig ist oder eine große Streuung aufweist.
Die :greedy
-Option für das schedule
-Argument ist seit Julia 1.11 verfügbar.
:static
Der :static
-Scheduler erstellt eine Aufgabe pro Thread und teilt die Iterationen gleichmäßig unter ihnen auf, wobei jeder Aufgabe spezifisch einem Thread zugewiesen wird. Insbesondere ist der Wert von threadid()
innerhalb einer Iteration konstant garantiert. Die Angabe von :static
ist ein Fehler, wenn sie innerhalb einer anderen @threads
-Schleife oder von einem anderen Thread als 1 verwendet wird.
Die :static
-Planung existiert zur Unterstützung des Übergangs von Code, der vor Julia 1.3 geschrieben wurde. In neu geschriebenen Bibliotheksfunktionen wird von der :static
-Planung abgeraten, da die Funktionen, die diese Option verwenden, nicht von beliebigen Arbeiter-Threads aufgerufen werden können.
Beispiele
Um die verschiedenen Scheduling-Strategien zu veranschaulichen, betrachten Sie die folgende Funktion busywait
, die eine nicht-yielding zeitgesteuerte Schleife enthält, die für eine bestimmte Anzahl von Sekunden läuft.
julia> function busywait(seconds)
tstart = time_ns()
while (time_ns() - tstart) / 1e9 < seconds
end
end
julia> @time begin
Threads.@spawn busywait(5)
Threads.@threads :static for i in 1:Threads.threadpoolsize()
busywait(1)
end
end
6.003001 Sekunden (16.33 k Zuweisungen: 899.255 KiB, 0.25% Kompilierungszeit)
julia> @time begin
Threads.@spawn busywait(5)
Threads.@threads :dynamic for i in 1:Threads.threadpoolsize()
busywait(1)
end
end
2.012056 Sekunden (16.05 k Zuweisungen: 883.919 KiB, 0.66% Kompilierungszeit)
Das :dynamic
-Beispiel benötigt 2 Sekunden, da einer der nicht besetzten Threads in der Lage ist, zwei der 1-Sekunden-Iterationen auszuführen, um die for-Schleife abzuschließen. ```
Base.Threads.foreach
— FunctionThreads.foreach(f, channel::Channel;
schedule::Threads.AbstractSchedule=Threads.FairSchedule(),
ntasks=Threads.threadpoolsize())
Ähnlich wie foreach(f, channel)
, aber die Iteration über channel
und die Aufrufe von f
werden auf ntasks
Aufgaben verteilt, die von Threads.@spawn
gestartet werden. Diese Funktion wartet darauf, dass alle intern gestarteten Aufgaben abgeschlossen sind, bevor sie zurückkehrt.
Wenn schedule isa FairSchedule
ist, wird Threads.foreach
versuchen, Aufgaben so zu starten, dass Julias Scheduler die Arbeitslast freier über die Threads ausbalancieren kann. Dieser Ansatz hat im Allgemeinen höhere Kosten pro Element, kann jedoch besser abschneiden als StaticSchedule
in Verbindung mit anderen mehrthreadigen Arbeitslasten.
Wenn schedule isa StaticSchedule
ist, wird Threads.foreach
Aufgaben so starten, dass die Kosten pro Element niedriger sind als bei FairSchedule
, aber weniger geeignet für die Lastenverteilung. Dieser Ansatz kann daher besser für feinkörnige, einheitliche Arbeitslasten geeignet sein, kann jedoch schlechter abschneiden als FairSchedule
in Verbindung mit anderen mehrthreadigen Arbeitslasten.
Beispiele
julia> n = 20
julia> c = Channel{Int}(ch -> foreach(i -> put!(ch, i), 1:n), 1)
julia> d = Channel{Int}(n) do ch
f = i -> put!(ch, i^2)
Threads.foreach(f, c)
end
julia> collect(d)
collect(d) = [1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361, 400]
Diese Funktion erfordert Julia 1.6 oder höher.
Base.Threads.@spawn
— MacroThreads.@spawn [:default|:interactive] expr
Erstellt eine Task
und schedule
sie zur Ausführung auf einem verfügbaren Thread im angegebenen Threadpool (:default
, wenn nicht angegeben). Die Aufgabe wird einem Thread zugewiesen, sobald einer verfügbar wird. Um auf den Abschluss der Aufgabe zu warten, rufen Sie wait
auf dem Ergebnis dieses Makros auf oder rufen Sie fetch
auf, um zu warten und dann den Rückgabewert zu erhalten.
Werte können über $
in @spawn
interpoliert werden, was den Wert direkt in die konstruierte zugrunde liegende Closure kopiert. Dies ermöglicht es Ihnen, den Wert einer Variablen einzufügen und den asynchronen Code von Änderungen am Wert der Variablen in der aktuellen Aufgabe zu isolieren.
Der Thread, auf dem die Aufgabe ausgeführt wird, kann sich ändern, wenn die Aufgabe yieldet, daher sollte threadid()
nicht als konstant für eine Aufgabe betrachtet werden. Siehe Task Migration
und das umfassendere Multi-Threading Handbuch für weitere wichtige Hinweise. Siehe auch das Kapitel über Threadpools.
Dieses Makro ist seit Julia 1.3 verfügbar.
Die Interpolation von Werten über $
ist seit Julia 1.4 verfügbar.
Ein Threadpool kann seit Julia 1.9 angegeben werden.
Beispiele
julia> t() = println("Hello from ", Threads.threadid());
julia> tasks = fetch.([Threads.@spawn t() for i in 1:4]);
Hello from 1
Hello from 1
Hello from 3
Hello from 4
Base.Threads.threadid
— FunctionThreads.threadid() -> Int
Holen Sie sich die ID-Nummer des aktuellen Ausführungsthreads. Der Master-Thread hat die ID 1
.
Beispiele
julia> Threads.threadid()
1
julia> Threads.@threads for i in 1:4
println(Threads.threadid())
end
4
2
5
4
!!! Hinweis Der Thread, auf dem eine Aufgabe ausgeführt wird, kann sich ändern, wenn die Aufgabe yieldet, was als Task Migration
bekannt ist. Aus diesem Grund ist es in den meisten Fällen nicht sicher, threadid()
zu verwenden, um beispielsweise auf einen Vektor von Puffern oder zustandsbehafteten Objekten zuzugreifen.
Base.Threads.maxthreadid
— FunctionThreads.maxthreadid() -> Int
Erhalten Sie eine untere Schranke für die Anzahl der Threads (über alle Thread-Pools) verfügbar für den Julia-Prozess, mit atomaren Erwerbssemantiken. Das Ergebnis wird immer größer oder gleich threadid()
sowie threadid(task)
für jede Aufgabe sein, die Sie beobachten konnten, bevor Sie maxthreadid
aufgerufen haben.
Base.Threads.nthreads
— FunctionThreads.nthreads(:default | :interactive) -> Int
Holen Sie sich die aktuelle Anzahl der Threads im angegebenen Thread-Pool. Die Threads in :interactive
haben ID-Nummern 1:nthreads(:interactive)
, und die Threads in :default
haben ID-Nummern in nthreads(:interactive) .+ (1:nthreads(:default))
.
Siehe auch BLAS.get_num_threads
und BLAS.set_num_threads
in der LinearAlgebra
Standardbibliothek, und nprocs()
in der Distributed
Standardbibliothek und Threads.maxthreadid()
.
Base.Threads.threadpool
— FunctionThreads.threadpool(tid = threadid()) -> Symbol
Gibt den Threadpool des angegebenen Threads zurück; entweder :default
, :interactive
oder :foreign
.
Base.Threads.nthreadpools
— FunctionThreads.nthreadpools() -> Int
Gibt die Anzahl der derzeit konfigurierten Thread-Pools zurück.
Base.Threads.threadpoolsize
— FunctionThreads.threadpoolsize(pool::Symbol = :default) -> Int
Erhalten Sie die Anzahl der Threads, die dem Standard-Thread-Pool (oder dem angegebenen Thread-Pool) zur Verfügung stehen.
Siehe auch: BLAS.get_num_threads
und BLAS.set_num_threads
in der LinearAlgebra
Standardbibliothek sowie nprocs()
in der Distributed
Standardbibliothek.
Base.Threads.ngcthreads
— FunctionThreads.ngcthreads() -> Int
Gibt die Anzahl der derzeit konfigurierten GC-Threads zurück. Dies umfasst sowohl Markierungs-Threads als auch gleichzeitige Sweep-Threads.
Siehe auch Multi-Threading.
Atomic operations
atomic
— KeywordUnsichere Zeigeroperationen sind kompatibel mit dem Laden und Speichern von Zeigern, die mit _Atomic
und std::atomic
Typ in C11 und C++23 deklariert sind. Ein Fehler kann auftreten, wenn es keine Unterstützung für das atomare Laden des Julia-Typs T
gibt.
Siehe auch: unsafe_load
, unsafe_modify!
, unsafe_replace!
, unsafe_store!
, unsafe_swap!
Base.@atomic
— Macro@atomic var
@atomic order ex
Markieren Sie var
oder ex
als atomar ausgeführt, wenn ex
ein unterstützter Ausdruck ist. Wenn keine order
angegeben ist, wird standardmäßig :sequentially_consistent verwendet.
@atomic a.b.x = new
@atomic a.b.x += addend
@atomic :release a.b.x = new
@atomic :acquire_release a.b.x += addend
Führen Sie die auf der rechten Seite ausgedrückte Speicheroperation atomar aus und geben Sie den neuen Wert zurück.
Mit =
übersetzt sich diese Operation in einen setproperty!(a.b, :x, new)
Aufruf. Mit jedem Operator übersetzt sich diese Operation ebenfalls in einen modifyproperty!(a.b, :x, +, addend)[2]
Aufruf.
@atomic a.b.x max arg2
@atomic a.b.x + arg2
@atomic max(a.b.x, arg2)
@atomic :acquire_release max(a.b.x, arg2)
@atomic :acquire_release a.b.x + arg2
@atomic :acquire_release a.b.x max arg2
Führen Sie die auf der rechten Seite ausgedrückte binäre Operation atomar aus. Speichern Sie das Ergebnis im Feld des ersten Arguments und geben Sie die Werte (old, new)
zurück.
Diese Operation übersetzt sich in einen modifyproperty!(a.b, :x, func, arg2)
Aufruf.
Siehe Per-field atomics Abschnitt im Handbuch für weitere Details.
Beispiele
julia> mutable struct Atomic{T}; @atomic x::T; end
julia> a = Atomic(1)
Atomic{Int64}(1)
julia> @atomic a.x # Feld x von a abrufen, mit sequentieller Konsistenz
1
julia> @atomic :sequentially_consistent a.x = 2 # Feld x von a setzen, mit sequentieller Konsistenz
2
julia> @atomic a.x += 1 # Feld x von a inkrementieren, mit sequentieller Konsistenz
3
julia> @atomic a.x + 1 # Feld x von a inkrementieren, mit sequentieller Konsistenz
3 => 4
julia> @atomic a.x # Feld x von a abrufen, mit sequentieller Konsistenz
4
julia> @atomic max(a.x, 10) # Feld x von a auf den Maximalwert ändern, mit sequentieller Konsistenz
4 => 10
julia> @atomic a.x max 5 # erneut Feld x von a auf den Maximalwert ändern, mit sequentieller Konsistenz
10 => 10
Diese Funktionalität erfordert mindestens Julia 1.7.
Base.@atomicswap
— Macro@atomicswap a.b.x = neu
@atomicswap :sequentially_consistent a.b.x = neu
Speichert neu
in a.b.x
und gibt den alten Wert von a.b.x
zurück.
Diese Operation übersetzt sich in einen swapproperty!(a.b, :x, neu)
Aufruf.
Siehe Per-field atomics Abschnitt im Handbuch für weitere Details.
Beispiele
julia> mutable struct Atomic{T}; @atomic x::T; end
julia> a = Atomic(1)
Atomic{Int64}(1)
julia> @atomicswap a.x = 2+2 # ersetze Feld x von a mit 4, mit sequentieller Konsistenz
1
julia> @atomic a.x # hole Feld x von a, mit sequentieller Konsistenz
4
Diese Funktionalität erfordert mindestens Julia 1.7.
Base.@atomicreplace
— Macro@atomicreplace a.b.x erwartet => gewünscht
@atomicreplace :sequentially_consistent a.b.x erwartet => gewünscht
@atomicreplace :sequentially_consistent :monotonic a.b.x erwartet => gewünscht
Führen Sie den bedingten Austausch, der durch das Paar ausgedrückt wird, atomar durch und geben Sie die Werte (alt, erfolg::Bool)
zurück. Dabei zeigt erfolg
, ob der Austausch abgeschlossen wurde.
Dieser Vorgang entspricht einem replaceproperty!(a.b, :x, erwartet, gewünscht)
Aufruf.
Siehe Per-field atomics Abschnitt im Handbuch für weitere Details.
Beispiele
julia> mutable struct Atomic{T}; @atomic x::T; end
julia> a = Atomic(1)
Atomic{Int64}(1)
julia> @atomicreplace a.x 1 => 2 # ersetze Feld x von a mit 2, wenn es 1 war, mit sequentieller Konsistenz
(alt = 1, erfolg = true)
julia> @atomic a.x # hole Feld x von a, mit sequentieller Konsistenz
2
julia> @atomicreplace a.x 1 => 2 # ersetze Feld x von a mit 2, wenn es 1 war, mit sequentieller Konsistenz
(alt = 2, erfolg = false)
julia> xchg = 2 => 0; # ersetze Feld x von a mit 0, wenn es 2 war, mit sequentieller Konsistenz
julia> @atomicreplace a.x xchg
(alt = 2, erfolg = true)
julia> @atomic a.x # hole Feld x von a, mit sequentieller Konsistenz
0
Diese Funktionalität erfordert mindestens Julia 1.7.
Base.@atomiconce
— Macro@atomiconce a.b.x = value
@atomiconce :sequentially_consistent a.b.x = value
@atomiconce :sequentially_consistent :monotonic a.b.x = value
Führen Sie die bedingte Zuweisung von value atomar durch, wenn es zuvor nicht gesetzt war, und geben Sie den Wert success::Bool
zurück. Dabei zeigt success
an, ob die Zuweisung abgeschlossen wurde.
Dieser Vorgang entspricht einem Aufruf von setpropertyonce!(a.b, :x, value)
.
Siehe Per-field atomics Abschnitt im Handbuch für weitere Details.
Beispiele
julia> mutable struct AtomicOnce
@atomic x
AtomicOnce() = new()
end
julia> a = AtomicOnce()
AtomicOnce(#undef)
julia> @atomiconce a.x = 1 # setze das Feld x von a auf 1, wenn es nicht gesetzt ist, mit sequentieller Konsistenz
true
julia> @atomic a.x # hole das Feld x von a, mit sequentieller Konsistenz
1
julia> @atomiconce a.x = 1 # setze das Feld x von a auf 1, wenn es nicht gesetzt ist, mit sequentieller Konsistenz
false
Diese Funktionalität erfordert mindestens Julia 1.11.
Core.AtomicMemory
— TypeAtomicMemory{T} == GenericMemory{:atomic, T, Core.CPU}
Feste Größe DenseVector{T}
. Der Zugriff auf eines seiner Elemente erfolgt atomar (mit :monotonic
Ordnung). Das Setzen eines der Elemente muss unter Verwendung des @atomic
Makros und unter expliziter Angabe der Ordnung erfolgen.
Jedes Element ist unabhängig atomar, wenn es zugegriffen wird, und kann nicht nicht-atomar gesetzt werden. Derzeit sind das @atomic
Makro und die höherstufige Schnittstelle noch nicht abgeschlossen, aber die Bausteine für eine zukünftige Implementierung sind die internen Intrinsics Core.memoryrefget
, Core.memoryrefset!
, Core.memoryref_isassigned
, Core.memoryrefswap!
, Core.memoryrefmodify!
und Core.memoryrefreplace!
.
Für Details siehe Atomare Operationen
Dieser Typ erfordert Julia 1.11 oder höher.
Es gibt auch optionale Parameter zur Speicheranordnung für die unsafe
-Funktionen, die die C/C++-kompatiblen Versionen dieser atomaren Operationen auswählen, wenn dieser Parameter auf unsafe_load
, unsafe_store!
, unsafe_swap!
, unsafe_replace!
und unsafe_modify!
festgelegt ist.
Die folgenden APIs sind veraltet, obwohl die Unterstützung für sie voraussichtlich in mehreren Versionen erhalten bleibt.
Base.Threads.Atomic
— TypeThreads.Atomic{T}
Hält eine Referenz auf ein Objekt des Typs T
und stellt sicher, dass es nur atomar, d.h. auf thread-sichere Weise, zugegriffen wird.
Nur bestimmte "einfache" Typen können atomar verwendet werden, nämlich die primitiven booleschen, ganzzahligen und Fließkomma-Typen. Diese sind Bool
, Int8
...Int128
, UInt8
...UInt128
und Float16
...Float64
.
Neue atomare Objekte können aus nicht-atomaren Werten erstellt werden; wenn nichts angegeben ist, wird das atomare Objekt mit null initialisiert.
Atomare Objekte können mit der []
-Notation zugegriffen werden:
Beispiele
julia> x = Threads.Atomic{Int}(3)
Base.Threads.Atomic{Int64}(3)
julia> x[] = 1
1
julia> x[]
1
Atomare Operationen verwenden ein atomic_
-Präfix, wie z.B. atomic_add!
, atomic_xchg!
usw.
Base.Threads.atomic_cas!
— FunctionThreads.atomic_cas!(x::Atomic{T}, cmp::T, newval::T) where T
Atomar vergleichen und setzen x
Vergleicht atomar den Wert in x
mit cmp
. Wenn sie gleich sind, wird newval
in x
geschrieben. Andernfalls bleibt x
unverändert. Gibt den alten Wert in x
zurück. Durch den Vergleich des zurückgegebenen Wertes mit cmp
(über ===
) weiß man, ob x
modifiziert wurde und jetzt den neuen Wert newval
enthält.
Für weitere Details siehe die cmpxchg
-Anweisung von LLVM.
Diese Funktion kann verwendet werden, um transaktionale Semantiken zu implementieren. Vor der Transaktion wird der Wert in x
aufgezeichnet. Nach der Transaktion wird der neue Wert nur gespeichert, wenn x
in der Zwischenzeit nicht modifiziert wurde.
Beispiele
julia> x = Threads.Atomic{Int}(3)
Base.Threads.Atomic{Int64}(3)
julia> Threads.atomic_cas!(x, 4, 2);
julia> x
Base.Threads.Atomic{Int64}(3)
julia> Threads.atomic_cas!(x, 3, 2);
julia> x
Base.Threads.Atomic{Int64}(2)
Base.Threads.atomic_xchg!
— FunctionThreads.atomic_xchg!(x::Atomic{T}, newval::T) where T
Atomar den Wert in x
austauschen
Austausch des Wertes in x
mit newval
auf atomare Weise. Gibt den alten Wert zurück.
Für weitere Details siehe die atomicrmw xchg
-Anweisung von LLVM.
Beispiele
julia> x = Threads.Atomic{Int}(3)
Base.Threads.Atomic{Int64}(3)
julia> Threads.atomic_xchg!(x, 2)
3
julia> x[]
2
Base.Threads.atomic_add!
— FunctionThreads.atomic_add!(x::Atomic{T}, val::T) where T <: ArithmeticTypes
Addiere val
atomar zu x
Führt x[] += val
atomar aus. Gibt den alten Wert zurück. Nicht definiert für Atomic{Bool}
.
Für weitere Details siehe LLVMs atomicrmw add
Anweisung.
Beispiele
julia> x = Threads.Atomic{Int}(3)
Base.Threads.Atomic{Int64}(3)
julia> Threads.atomic_add!(x, 2)
3
julia> x[]
5
Base.Threads.atomic_sub!
— FunctionThreads.atomic_sub!(x::Atomic{T}, val::T) where T <: ArithmeticTypes
Zieht val
atomar von x
ab.
Führt x[] -= val
atomar aus. Gibt den alten Wert zurück. Nicht definiert für Atomic{Bool}
.
Für weitere Details siehe LLVMs atomicrmw sub
-Anweisung.
Beispiele
julia> x = Threads.Atomic{Int}(3)
Base.Threads.Atomic{Int64}(3)
julia> Threads.atomic_sub!(x, 2)
3
julia> x[]
1
Base.Threads.atomic_and!
— FunctionThreads.atomic_and!(x::Atomic{T}, val::T) where T
Atomar bitweise-UND x
mit val
Führt x[] &= val
atomar aus. Gibt den alten Wert zurück.
Für weitere Details siehe LLVMs atomicrmw and
-Anweisung.
Beispiele
julia> x = Threads.Atomic{Int}(3)
Base.Threads.Atomic{Int64}(3)
julia> Threads.atomic_and!(x, 2)
3
julia> x[]
2
Base.Threads.atomic_nand!
— FunctionThreads.atomic_nand!(x::Atomic{T}, val::T) where T
Atomar bitweise nand (nicht-und) x
mit val
Führt x[] = ~(x[] & val)
atomar aus. Gibt den alten Wert zurück.
Für weitere Details siehe LLVMs atomicrmw nand
Anweisung.
Beispiele
julia> x = Threads.Atomic{Int}(3)
Base.Threads.Atomic{Int64}(3)
julia> Threads.atomic_nand!(x, 2)
3
julia> x[]
-3
Base.Threads.atomic_or!
— FunctionThreads.atomic_or!(x::Atomic{T}, val::T) where T
Atomar bitweise oder x
mit val
Führt x[] |= val
atomar aus. Gibt den alten Wert zurück.
Für weitere Details siehe LLVMs atomicrmw or
Anweisung.
Beispiele
julia> x = Threads.Atomic{Int}(5)
Base.Threads.Atomic{Int64}(5)
julia> Threads.atomic_or!(x, 7)
5
julia> x[]
7
Base.Threads.atomic_xor!
— FunctionThreads.atomic_xor!(x::Atomic{T}, val::T) where T
Atomar bitweise XOR (exklusives Oder) x
mit val
Führt x[] $= val
atomar aus. Gibt den alten Wert zurück.
Für weitere Details siehe LLVMs atomicrmw xor
-Anweisung.
Beispiele
julia> x = Threads.Atomic{Int}(5)
Base.Threads.Atomic{Int64}(5)
julia> Threads.atomic_xor!(x, 7)
5
julia> x[]
2
Base.Threads.atomic_max!
— FunctionThreads.atomic_max!(x::Atomic{T}, val::T) where T
Speichert atomar das Maximum von x
und val
in x
Führt x[] = max(x[], val)
atomar aus. Gibt den alten Wert zurück.
Für weitere Details siehe LLVMs atomicrmw max
-Anweisung.
Beispiele
julia> x = Threads.Atomic{Int}(5)
Base.Threads.Atomic{Int64}(5)
julia> Threads.atomic_max!(x, 7)
5
julia> x[]
7
Base.Threads.atomic_min!
— FunctionThreads.atomic_min!(x::Atomic{T}, val::T) where T
Speichert atomar das Minimum von x
und val
in x
Führt x[] = min(x[], val)
atomar aus. Gibt den alten Wert zurück.
Für weitere Details siehe LLVMs atomicrmw min
-Anweisung.
Beispiele
julia> x = Threads.Atomic{Int}(7)
Base.Threads.Atomic{Int64}(7)
julia> Threads.atomic_min!(x, 5)
7
julia> x[]
5
Base.Threads.atomic_fence
— FunctionThreads.atomic_fence()
Fügen Sie einen Speicherzaun mit sequentieller Konsistenz ein
Fügt einen Speicherzaun mit sequentiell konsistenten Ordnungssemantiken ein. Es gibt Algorithmen, bei denen dies erforderlich ist, d.h. bei denen eine Acquire/Release-Ordnung nicht ausreicht.
Dies ist wahrscheinlich eine sehr kostspielige Operation. Da alle anderen atomaren Operationen in Julia bereits über Acquire/Release-Semantiken verfügen, sollten explizite Zäune in den meisten Fällen nicht erforderlich sein.
Für weitere Details siehe die fence
-Anweisung von LLVM.
ccall using a libuv threadpool (Experimental)
Base.@threadcall
— Macro@threadcall((cfunc, clib), rettype, (argtypes...), argvals...)
Das @threadcall
-Makro wird auf die gleiche Weise wie ccall
aufgerufen, führt die Arbeit jedoch in einem anderen Thread aus. Dies ist nützlich, wenn Sie eine blockierende C-Funktion aufrufen möchten, ohne den aktuellen julia
-Thread zu blockieren. Die Parallelität ist durch die Größe des libuv-Thread-Pools begrenzt, der standardmäßig 4 Threads umfasst, aber durch Setzen der Umgebungsvariable UV_THREADPOOL_SIZE
erhöht werden kann, und der julia
-Prozess muss neu gestartet werden.
Beachten Sie, dass die aufgerufene Funktion niemals in Julia zurückrufen sollte.
Low-level synchronization primitives
Diese Bausteine werden verwendet, um die regulären Synchronisationsobjekte zu erstellen.
Base.Threads.SpinLock
— TypeSpinLock()
Erstellen Sie ein nicht-reentrantes Spinlock mit Test-and-Test-and-Set. Rekursive Verwendung führt zu einem Deadlock. Diese Art von Lock sollte nur um Code verwendet werden, der wenig Zeit in Anspruch nimmt und nicht blockiert (z. B. I/O durchführt). Im Allgemeinen sollte stattdessen ReentrantLock
verwendet werden.
Jeder lock
muss mit einem unlock
übereinstimmen. Wenn !islocked(lck::SpinLock)
gilt, gelingt trylock(lck)
, es sei denn, es gibt andere Aufgaben, die versuchen, das Lock "zur gleichen Zeit" zu halten.
Test-and-Test-and-Set Spinlocks sind bis zu etwa 30 konkurrierenden Threads am schnellsten. Wenn Sie mehr Konkurrenz als das haben, sollten andere Synchronisationsansätze in Betracht gezogen werden.