Multi-Threading
Base.Threads.@threads
— MacroThreads.@threads [schedule] for ... end
병렬로 for
루프를 실행하는 매크로입니다. 반복 공간은 거칠게 분할된 작업에 분배됩니다. 이 정책은 schedule
인수로 지정할 수 있습니다. 루프의 실행은 모든 반복의 평가를 기다립니다.
참고: @spawn
및 Distributed
의 pmap
.
확장 도움말
의미론
스케줄링 옵션에 의해 더 강력한 보장이 지정되지 않는 한, @threads
매크로에 의해 실행되는 루프는 다음과 같은 의미론을 가집니다.
@threads
매크로는 루프 본문을 불특정한 순서로 잠재적으로 동시에 실행합니다. 작업과 작업자 스레드의 정확한 할당을 지정하지 않습니다. 할당은 각 실행마다 다를 수 있습니다. 루프 본문 코드(그로부터 전이적으로 호출되는 모든 코드를 포함)는 반복이 작업에 분배되는 방식이나 실행되는 작업자 스레드에 대한 가정을 해서는 안 됩니다. 각 반복의 루프 본문은 다른 반복과 독립적으로 진행할 수 있어야 하며 데이터 경쟁으로부터 자유로워야 합니다. 따라서 반복 간의 잘못된 동기화는 교착 상태를 초래할 수 있으며, 동기화되지 않은 메모리 접근은 정의되지 않은 동작을 초래할 수 있습니다.
예를 들어, 위의 조건은 다음을 의미합니다:
- 반복에서 획득한 잠금은 반드시 같은 반복 내에서 해제되어야 합니다.
Channel
과 같은 차단 원시를 사용하여 반복 간에 통신하는 것은 잘못된 것입니다.- 반복 간에 공유되지 않는 위치에만 쓰기(잠금 또는 원자적 작업이 사용되지 않는 한).
:static
스케줄이 사용되지 않는 한,threadid()
의 값은 단일 반복 내에서도 변경될 수 있습니다.Task Migration
을 참조하십시오.
스케줄러
스케줄러 인수가 없으면 정확한 스케줄링은 지정되지 않으며 Julia 릴리스에 따라 다릅니다. 현재는 스케줄러가 지정되지 않은 경우 :dynamic
이 사용됩니다.
schedule
인수는 Julia 1.5부터 사용할 수 있습니다.
:dynamic
(기본값)
:dynamic
스케줄러는 사용 가능한 작업자 스레드에 대해 반복을 동적으로 실행합니다. 현재 구현은 각 반복의 작업 부하가 균일하다고 가정합니다. 그러나 이 가정은 미래에 제거될 수 있습니다.
이 스케줄링 옵션은 기본 실행 메커니즘에 대한 힌트일 뿐입니다. 그러나 몇 가지 속성을 기대할 수 있습니다. :dynamic
스케줄러가 사용하는 Task
의 수는 사용 가능한 작업자 스레드의 수의 작은 상수 배수로 제한됩니다 (Threads.threadpoolsize()
). 각 작업은 반복 공간의 인접한 영역을 처리합니다. 따라서 @threads :dynamic for x in xs; f(x); end
는 일반적으로 @sync for x in xs; @spawn f(x); end
보다 더 효율적입니다. 여기서 length(xs)
는 작업자 스레드의 수보다 훨씬 크고 f(x)
의 실행 시간은 작업을 생성하고 동기화하는 비용보다 상대적으로 작습니다(일반적으로 10마이크로초 미만).
schedule
인수에 대한 :dynamic
옵션은 Julia 1.8부터 사용 가능하며 기본값입니다.
:greedy
:greedy
스케줄러는 최대 Threads.threadpoolsize()
작업을 생성하며, 각 작업은 생성되는 주어진 반복 값에 대해 탐욕적으로 작업합니다. 하나의 작업이 작업을 마치면 다음 값을 반복자에서 가져옵니다. 개별 작업이 수행하는 작업은 반복자에서 인접한 값에 대해 반드시 수행되는 것은 아닙니다. 주어진 반복자는 영원히 값을 생성할 수 있으며, 반복자 인터페이스만 필요합니다(인덱싱 없음).
이 스케줄링 옵션은 일반적으로 개별 반복의 작업 부하가 균일하지 않거나 큰 분포를 가질 때 좋은 선택입니다.
schedule
인수에 대한 :greedy
옵션은 Julia 1.11부터 사용 가능합니다.
:static
:static
스케줄러는 각 스레드당 하나의 작업을 생성하고 반복을 균등하게 나누어 각 작업을 특정 스레드에 할당합니다. 특히, threadid()
의 값은 하나의 반복 내에서 일정하다고 보장됩니다. 다른 @threads
루프 내에서 사용하거나 1이 아닌 스레드에서 사용하면 :static
을 지정하는 것은 오류입니다.
:static
스케줄링은 Julia 1.3 이전에 작성된 코드의 전환을 지원하기 위해 존재합니다. 새로 작성된 라이브러리 함수에서는 이 옵션을 사용하는 것이 권장되지 않습니다. 이 옵션을 사용하는 함수는 임의의 작업자 스레드에서 호출할 수 없습니다.
예제
다양한 스케줄링 전략을 설명하기 위해, 주어진 초 수 동안 실행되는 비양보 타이머 루프를 포함하는 다음 함수 busywait
를 고려하십시오.
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 seconds (16.33 k allocations: 899.255 KiB, 0.25% compilation time)
julia> @time begin
Threads.@spawn busywait(5)
Threads.@threads :dynamic for i in 1:Threads.threadpoolsize()
busywait(1)
end
end
2.012056 seconds (16.05 k allocations: 883.919 KiB, 0.66% compilation time)
:dynamic
예제는 2초가 걸리며, 비어 있는 스레드 중 하나가 1초 반복 두 개를 실행하여 for 루프를 완료할 수 있습니다. ```
Base.Threads.foreach
— FunctionThreads.foreach(f, channel::Channel;
schedule::Threads.AbstractSchedule=Threads.FairSchedule(),
ntasks=Threads.threadpoolsize())
foreach(f, channel)
와 유사하지만, channel
에 대한 반복과 f
에 대한 호출이 Threads.@spawn
에 의해 생성된 ntasks
작업에 분산됩니다. 이 함수는 내부적으로 생성된 모든 작업이 완료될 때까지 기다린 후 반환됩니다.
schedule isa FairSchedule
인 경우, Threads.foreach
는 Julia의 스케줄러가 스레드 간에 작업 항목을 보다 자유롭게 로드 밸런싱할 수 있도록 작업을 생성하려고 시도합니다. 이 접근 방식은 일반적으로 항목당 오버헤드가 더 높지만, 다른 멀티스레드 작업과 동시에 수행할 때 StaticSchedule
보다 더 나은 성능을 발휘할 수 있습니다.
schedule isa StaticSchedule
인 경우, Threads.foreach
는 FairSchedule
보다 항목당 오버헤드가 낮은 방식으로 작업을 생성하지만, 로드 밸런싱에는 덜 적합합니다. 따라서 이 접근 방식은 세밀하고 균일한 작업에 더 적합할 수 있지만, 다른 멀티스레드 작업과 동시에 수행할 때 FairSchedule
보다 성능이 떨어질 수 있습니다.
예제
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 이상이 필요합니다.
Base.Threads.@spawn
— MacroThreads.@spawn [:default|:interactive] expr
Task
를 생성하고 지정된 스레드 풀에서 사용 가능한 스레드에서 실행되도록 schedule
합니다(:default
가 지정되지 않은 경우). 작업은 스레드가 사용 가능해지면 해당 스레드에 할당됩니다. 작업이 완료될 때까지 기다리려면 이 매크로의 결과에 대해 wait
를 호출하거나, fetch
를 호출하여 기다린 후 반환 값을 얻습니다.
값은 $
를 통해 @spawn
에 삽입될 수 있으며, 이는 값을 생성된 기본 클로저에 직접 복사합니다. 이를 통해 변수의 값을 삽입할 수 있으며, 현재 작업에서 변수의 값 변경으로부터 비동기 코드를 격리할 수 있습니다.
작업이 실행되는 스레드는 작업이 양보할 경우 변경될 수 있으므로, threadid()
는 작업에 대해 상수로 취급해서는 안 됩니다. Task Migration
및 더 넓은 multi-threading 매뉴얼에서 추가적인 중요한 주의 사항을 참조하십시오. threadpools 장도 참조하십시오.
이 매크로는 Julia 1.3부터 사용할 수 있습니다.
$
를 통한 값의 인터폴레이션은 Julia 1.4부터 사용할 수 있습니다.
스레드 풀은 Julia 1.9부터 지정할 수 있습니다.
예제
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
현재 실행 중인 스레드의 ID 번호를 가져옵니다. 마스터 스레드는 ID 1
을 가집니다.
예제
julia> Threads.threadid()
1
julia> Threads.@threads for i in 1:4
println(Threads.threadid())
end
4
2
5
4
작업이 양보할 경우 작업이 실행되는 스레드는 변경될 수 있으며, 이는 Task Migration
으로 알려져 있습니다. 이러한 이유로 대부분의 경우 threadid()
를 사용하여 버퍼 또는 상태 객체의 벡터에 인덱싱하는 것은 안전하지 않습니다.
Base.Threads.maxthreadid
— FunctionThreads.maxthreadid() -> Int
Julia 프로세스에서 사용 가능한 스레드 수(모든 스레드 풀에 걸쳐)에 대한 하한을 가져옵니다. 원자적 획득 의미론을 사용합니다. 결과는 항상 threadid()
및 threadid(task)
보다 크거나 같으며, 이는 maxthreadid
를 호출하기 전에 관찰할 수 있었던 모든 작업에 해당합니다.
Base.Threads.nthreads
— FunctionThreads.nthreads(:default | :interactive) -> Int
지정된 스레드 풀 내에서 현재 스레드 수를 가져옵니다. :interactive
의 스레드는 ID 번호가 1:nthreads(:interactive)
이고, :default
의 스레드는 ID 번호가 nthreads(:interactive) .+ (1:nthreads(:default))
입니다.
또한 LinearAlgebra
표준 라이브러리의 BLAS.get_num_threads
및 BLAS.set_num_threads
, 그리고 Distributed
표준 라이브러리의 nprocs()
및 Threads.maxthreadid()
를 참조하십시오.
Base.Threads.threadpool
— FunctionThreads.threadpool(tid = threadid()) -> Symbol
지정된 스레드의 스레드 풀을 반환합니다. :default
, :interactive
또는 :foreign
중 하나입니다.
Base.Threads.nthreadpools
— FunctionThreads.nthreadpools() -> Int
현재 구성된 스레드 풀의 수를 반환합니다.
Base.Threads.threadpoolsize
— FunctionThreads.threadpoolsize(pool::Symbol = :default) -> Int
기본 스레드 풀(또는 지정된 스레드 풀)에 사용할 수 있는 스레드 수를 가져옵니다.
또한, LinearAlgebra
표준 라이브러리의 BLAS.get_num_threads
및 BLAS.set_num_threads
, 그리고 Distributed
표준 라이브러리의 nprocs()
를 참조하십시오.
Base.Threads.ngcthreads
— FunctionThreads.ngcthreads() -> Int
현재 구성된 GC 스레드의 수를 반환합니다. 여기에는 마크 스레드와 동시 스위프 스레드가 모두 포함됩니다.
또한 Multi-Threading를 참조하세요.
Atomic operations
atomic
— Keyword안전하지 않은 포인터 작업은 C11 및 C++23에서 각각 _Atomic
및 std::atomic
유형으로 선언된 포인터를 로드하고 저장하는 것과 호환됩니다. Julia 유형 T
를 원자적으로 로드하는 것을 지원하지 않는 경우 오류가 발생할 수 있습니다.
참고: unsafe_load
, unsafe_modify!
, unsafe_replace!
, unsafe_store!
, unsafe_swap!
Base.@atomic
— Macro@atomic var
@atomic order ex
var
또는 ex
를 원자적으로 수행되도록 표시합니다. ex
가 지원되는 표현식인 경우에만 해당합니다. order
가 지정되지 않으면 기본값은 :sequentially_consistent입니다.
@atomic a.b.x = new
@atomic a.b.x += addend
@atomic :release a.b.x = new
@atomic :acquire_release a.b.x += addend
오른쪽에 표현된 저장 작업을 원자적으로 수행하고 새 값을 반환합니다.
=
를 사용하면 이 작업은 setproperty!(a.b, :x, new)
호출로 변환됩니다. 다른 연산자를 사용할 경우 이 작업은 modifyproperty!(a.b, :x, +, addend)[2]
호출로 변환됩니다.
@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
오른쪽에 표현된 이진 작업을 원자적으로 수행합니다. 결과를 첫 번째 인수의 필드에 저장하고 값 (old, new)
를 반환합니다.
이 작업은 modifyproperty!(a.b, :x, func, arg2)
호출로 변환됩니다.
자세한 내용은 매뉴얼의 Per-field atomics 섹션을 참조하십시오.
예제
julia> mutable struct Atomic{T}; @atomic x::T; end
julia> a = Atomic(1)
Atomic{Int64}(1)
julia> @atomic a.x # a의 필드 x를 가져오고, 순차적 일관성으로
1
julia> @atomic :sequentially_consistent a.x = 2 # a의 필드 x를 설정하고, 순차적 일관성으로
2
julia> @atomic a.x += 1 # a의 필드 x를 증가시키고, 순차적 일관성으로
3
julia> @atomic a.x + 1 # a의 필드 x를 증가시키고, 순차적 일관성으로
3 => 4
julia> @atomic a.x # a의 필드 x를 가져오고, 순차적 일관성으로
4
julia> @atomic max(a.x, 10) # a의 필드 x를 최대값으로 변경하고, 순차적 일관성으로
4 => 10
julia> @atomic a.x max 5 # 다시 a의 필드 x를 최대값으로 변경하고, 순차적 일관성으로
10 => 10
이 기능은 최소한 Julia 1.7이 필요합니다.
Base.@atomicswap
— Macro@atomicswap a.b.x = new
@atomicswap :sequentially_consistent a.b.x = new
new
을 a.b.x
에 저장하고 a.b.x
의 이전 값을 반환합니다.
이 작업은 swapproperty!(a.b, :x, new)
호출로 변환됩니다.
자세한 내용은 매뉴얼의 Per-field atomics 섹션을 참조하세요.
예제
julia> mutable struct Atomic{T}; @atomic x::T; end
julia> a = Atomic(1)
Atomic{Int64}(1)
julia> @atomicswap a.x = 2+2 # sequential consistency로 a의 필드 x를 4로 교체
1
julia> @atomic a.x # sequential consistency로 a의 필드 x를 가져옴
4
이 기능은 최소한 Julia 1.7이 필요합니다.
Base.@atomicreplace
— Macro@atomicreplace a.b.x expected => desired
@atomicreplace :sequentially_consistent a.b.x expected => desired
@atomicreplace :sequentially_consistent :monotonic a.b.x expected => desired
조건부 교체를 쌍으로 원자적으로 수행하여 (old, success::Bool)
값을 반환합니다. 여기서 success
는 교체가 완료되었는지를 나타냅니다.
이 작업은 replaceproperty!(a.b, :x, expected, desired)
호출로 변환됩니다.
자세한 내용은 매뉴얼의 Per-field atomics 섹션을 참조하세요.
예제
julia> mutable struct Atomic{T}; @atomic x::T; end
julia> a = Atomic(1)
Atomic{Int64}(1)
julia> @atomicreplace a.x 1 => 2 # a의 필드 x를 1일 경우 2로 교체, 순차적 일관성 유지
(old = 1, success = true)
julia> @atomic a.x # a의 필드 x를 가져오기, 순차적 일관성 유지
2
julia> @atomicreplace a.x 1 => 2 # a의 필드 x를 1일 경우 2로 교체, 순차적 일관성 유지
(old = 2, success = false)
julia> xchg = 2 => 0; # a의 필드 x를 2일 경우 0으로 교체, 순차적 일관성 유지
julia> @atomicreplace a.x xchg
(old = 2, success = true)
julia> @atomic a.x # a의 필드 x를 가져오기, 순차적 일관성 유지
0
이 기능은 최소한 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
값이 이전에 설정되지 않은 경우, 조건부 할당을 원자적으로 수행하고 success::Bool
값을 반환합니다. 여기서 success
는 할당이 완료되었는지를 나타냅니다.
이 작업은 setpropertyonce!(a.b, :x, value)
호출로 변환됩니다.
자세한 내용은 매뉴얼의 Per-field atomics 섹션을 참조하십시오.
예제
julia> mutable struct AtomicOnce
@atomic x
AtomicOnce() = new()
end
julia> a = AtomicOnce()
AtomicOnce(#undef)
julia> @atomiconce a.x = 1 # 설정되지 않은 경우, 순차적 일관성으로 a의 필드 x를 1로 설정
true
julia> @atomic a.x # 순차적 일관성으로 a의 필드 x를 가져오기
1
julia> @atomiconce a.x = 1 # 설정되지 않은 경우, 순차적 일관성으로 a의 필드 x를 1로 설정
false
이 기능은 최소한 Julia 1.11이 필요합니다.
```
Core.AtomicMemory
— TypeAtomicMemory{T} == GenericMemory{:atomic, T, Core.CPU}
고정 크기 DenseVector{T}
. 그 요소에 대한 접근은 원자적으로 수행됩니다( :monotonic
순서로). 요소 중 하나를 설정하려면 @atomic
매크로를 사용하고 명시적으로 순서를 지정해야 합니다.
!!! 경고 각 요소는 접근할 때 독립적으로 원자적이며 비원자적으로 설정할 수 없습니다. 현재 @atomic
매크로와 고급 인터페이스는 완료되지 않았지만, 향후 구현을 위한 빌딩 블록은 내부 내장 함수 Core.memoryrefget
, Core.memoryrefset!
, Core.memoryref_isassigned
, Core.memoryrefswap!
, Core.memoryrefmodify!
, 및 Core.memoryrefreplace!
입니다.
자세한 내용은 Atomic Operations를 참조하십시오.
!!! 호환성 "Julia 1.11" 이 유형은 Julia 1.11 이상이 필요합니다.
unsafe
함수 집합에 대한 선택적 메모리 정렬 매개변수가 있으며, 이 매개변수가 지정되면 이러한 원자적 작업의 C/C++ 호환 버전을 선택합니다. 매개변수는 다음과 같습니다: unsafe_load
, unsafe_store!
, unsafe_swap!
, unsafe_replace!
, 및 unsafe_modify!
.
다음 API는 더 이상 사용되지 않지만, 여러 릴리스 동안 지원이 유지될 가능성이 높습니다.
Base.Threads.Atomic
— TypeThreads.Atomic{T}
타입 T
의 객체에 대한 참조를 보유하며, 이는 원자적으로 접근되도록 보장합니다. 즉, 스레드 안전한 방식으로 접근됩니다.
원자적으로 사용할 수 있는 "단순한" 타입은 제한적이며, 기본적인 불리언, 정수 및 부동 소수점 타입입니다. 이들은 Bool
, Int8
...Int128
, UInt8
...UInt128
, 및 Float16
...Float64
입니다.
새로운 원자 객체는 비원자 값에서 생성할 수 있으며, 지정하지 않으면 원자 객체는 0으로 초기화됩니다.
원자 객체는 []
표기법을 사용하여 접근할 수 있습니다:
예제
julia> x = Threads.Atomic{Int}(3)
Base.Threads.Atomic{Int64}(3)
julia> x[] = 1
1
julia> x[]
1
원자 작업은 atomic_
접두사를 사용하며, 예를 들어 atomic_add!
, atomic_xchg!
등이 있습니다.
Base.Threads.atomic_cas!
— FunctionThreads.atomic_cas!(x::Atomic{T}, cmp::T, newval::T) where T
원자적으로 x
를 비교하고 설정합니다.
x
의 값을 cmp
와 원자적으로 비교합니다. 같으면 newval
을 x
에 씁니다. 그렇지 않으면 x
는 수정되지 않은 채로 남습니다. x
의 이전 값을 반환합니다. 반환된 값을 cmp
와 비교(===
를 통해)함으로써 x
가 수정되었는지, 이제 새로운 값 newval
을 가지고 있는지를 알 수 있습니다.
자세한 내용은 LLVM의 cmpxchg
명령어를 참조하십시오.
이 함수는 트랜잭셔널 의미론을 구현하는 데 사용할 수 있습니다. 트랜잭션 전에 x
의 값을 기록합니다. 트랜잭션 후, 그 동안 x
가 수정되지 않았다면 새로운 값이 저장됩니다.
예제
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
x
의 값을 원자적으로 교환합니다.
x
의 값을 newval
과 원자적으로 교환합니다. 이전 값을 반환합니다.
자세한 내용은 LLVM의 atomicrmw xchg
명령어를 참조하십시오.
예제
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
val
을 x
에 원자적으로 더합니다.
x[] += val
을 원자적으로 수행합니다. 이전 값을 반환합니다. Atomic{Bool}
에 대해서는 정의되어 있지 않습니다.
자세한 내용은 LLVM의 atomicrmw add
명령어를 참조하십시오.
예제
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
val
을 x
에서 원자적으로 빼기
원자적으로 x[] -= val
을 수행합니다. 이전 값을 반환합니다. Atomic{Bool}
에 대해서는 정의되어 있지 않습니다.
자세한 내용은 LLVM의 atomicrmw sub
명령어를 참조하십시오.
예제
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
x
와 val
을 원자적으로 비트 단위 AND 연산합니다.
원자적으로 x[] &= val
을 수행합니다. 이전 값을 반환합니다.
자세한 내용은 LLVM의 atomicrmw and
명령어를 참조하십시오.
예제
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
원자적으로 비트 단위 nand (not-and) 연산을 x
와 val
에 수행합니다.
x[] = ~(x[] & val)
를 원자적으로 수행합니다. 이전 값을 반환합니다.
자세한 내용은 LLVM의 atomicrmw nand
명령어를 참조하십시오.
예제
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
x
를 val
과 원자적으로 비트wise-or합니다.
원자적으로 x[] |= val
을 수행합니다. 이전 값을 반환합니다.
자세한 내용은 LLVM의 atomicrmw or
명령어를 참조하십시오.
예제
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
원자적으로 x
와 val
을 비트 단위 XOR(배타적 OR)합니다.
x[] $= val
을 원자적으로 수행합니다. 이전 값을 반환합니다.
자세한 내용은 LLVM의 atomicrmw xor
명령어를 참조하십시오.
예제
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
x
와 val
의 최대값을 원자적으로 x
에 저장합니다.
원자적으로 x[] = max(x[], val)
을 수행합니다. 이전 값을 반환합니다.
자세한 내용은 LLVM의 atomicrmw max
명령어를 참조하십시오.
예제
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
x
와 val
의 최소값을 원자적으로 x
에 저장합니다.
x[] = min(x[], val)
을 원자적으로 수행합니다. 이전 값을 반환합니다.
자세한 내용은 LLVM의 atomicrmw min
명령어를 참조하십시오.
예제
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()
순차적 일관성 메모리 펜스 삽입
순차적으로 일관된 순서 의미를 가진 메모리 펜스를 삽입합니다. 이는 필요할 수 있는 알고리즘이 있으며, 즉 acquire/release 순서만으로는 충분하지 않은 경우입니다.
이는 아마도 매우 비싼 작업일 것입니다. Julia의 다른 모든 원자 작업이 이미 acquire/release 의미를 가지고 있으므로, 대부분의 경우 명시적인 펜스는 필요하지 않을 것입니다.
자세한 내용은 LLVM의 fence
명령어를 참조하십시오.
ccall using a libuv threadpool (Experimental)
Base.@threadcall
— Macro@threadcall((cfunc, clib), rettype, (argtypes...), argvals...)
@threadcall
매크로는 ccall
와 같은 방식으로 호출되지만, 다른 스레드에서 작업을 수행합니다. 이는 현재 julia
스레드가 차단되지 않도록 차단 C 함수를 호출하고자 할 때 유용합니다. 동시성은 libuv 스레드 풀의 크기에 의해 제한되며, 기본값은 4개의 스레드이지만 UV_THREADPOOL_SIZE
환경 변수를 설정하고 julia
프로세스를 재시작하여 늘릴 수 있습니다.
호출된 함수는 절대 Julia로 다시 호출해서는 안 됩니다.
Low-level synchronization primitives
이 빌딩 블록은 정기적인 동기화 객체를 생성하는 데 사용됩니다.
Base.Threads.SpinLock
— TypeSpinLock()
비재진입, 테스트-테스트-세트 스핀 잠금을 생성합니다. 재귀적 사용은 교착 상태를 초래합니다. 이러한 종류의 잠금은 실행하는 데 시간이 거의 걸리지 않고 차단하지 않는 코드(예: I/O 수행) 주위에서만 사용해야 합니다. 일반적으로 ReentrantLock
을 대신 사용해야 합니다.
각 lock
은 unlock
과 쌍을 이루어야 합니다. !islocked(lck::SpinLock)
이 참이면, trylock(lck)
은 "동시에" 잠금을 보유하려고 시도하는 다른 작업이 없으면 성공합니다.
테스트-테스트-세트 스핀 잠금은 약 30개 정도의 경쟁 스레드까지 가장 빠릅니다. 그보다 더 많은 경쟁이 있는 경우, 다른 동기화 접근 방식을 고려해야 합니다.