Multi-Threading

Base.Threads.@threadsMacro
Threads.@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 Channels 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 von threadid() selbst innerhalb einer einzigen Iteration variieren. Siehe Task 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.

Julia 1.5

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 Tasks 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).

Julia 1.8

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.

Julia 1.11

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.

Note

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. ```

source
Base.Threads.foreachFunction
Threads.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]
Julia 1.6

Diese Funktion erfordert Julia 1.6 oder höher.

source
Base.Threads.@spawnMacro
Threads.@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.

Note

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.

Julia 1.3

Dieses Makro ist seit Julia 1.3 verfügbar.

Julia 1.4

Die Interpolation von Werten über $ ist seit Julia 1.4 verfügbar.

Julia 1.9

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
source
Base.Threads.threadidFunction
Threads.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.

source
Base.Threads.maxthreadidFunction
Threads.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.

source
Base.Threads.nthreadsFunction
Threads.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().

source
Base.Threads.threadpoolFunction
Threads.threadpool(tid = threadid()) -> Symbol

Gibt den Threadpool des angegebenen Threads zurück; entweder :default, :interactive oder :foreign.

source
Base.Threads.threadpoolsizeFunction
Threads.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.

source
Base.Threads.ngcthreadsFunction
Threads.ngcthreads() -> Int

Gibt die Anzahl der derzeit konfigurierten GC-Threads zurück. Dies umfasst sowohl Markierungs-Threads als auch gleichzeitige Sweep-Threads.

source

Siehe auch Multi-Threading.

Atomic operations

Base.@atomicMacro
@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
Julia 1.7

Diese Funktionalität erfordert mindestens Julia 1.7.

source
Base.@atomicswapMacro
@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
Julia 1.7

Diese Funktionalität erfordert mindestens Julia 1.7.

source
Base.@atomicreplaceMacro
@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
Julia 1.7

Diese Funktionalität erfordert mindestens Julia 1.7.

source
Base.@atomiconceMacro
@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
Julia 1.11

Diese Funktionalität erfordert mindestens Julia 1.11.

source
Core.AtomicMemoryType
AtomicMemory{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.

Warning

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

Julia 1.11

Dieser Typ erfordert Julia 1.11 oder höher.

source

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.

Warning

Die folgenden APIs sind veraltet, obwohl die Unterstützung für sie voraussichtlich in mehreren Versionen erhalten bleibt.

Base.Threads.AtomicType
Threads.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.

source
Base.Threads.atomic_cas!Function
Threads.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)
source
Base.Threads.atomic_xchg!Function
Threads.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
source
Base.Threads.atomic_add!Function
Threads.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
source
Base.Threads.atomic_sub!Function
Threads.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
source
Base.Threads.atomic_and!Function
Threads.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
source
Base.Threads.atomic_nand!Function
Threads.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
source
Base.Threads.atomic_or!Function
Threads.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
source
Base.Threads.atomic_xor!Function
Threads.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
source
Base.Threads.atomic_max!Function
Threads.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
source
Base.Threads.atomic_min!Function
Threads.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
source
Base.Threads.atomic_fenceFunction
Threads.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.

source

ccall using a libuv threadpool (Experimental)

Base.@threadcallMacro
@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.

source

Low-level synchronization primitives

Diese Bausteine werden verwendet, um die regulären Synchronisationsobjekte zu erstellen.

Base.Threads.SpinLockType
SpinLock()

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.

source