Многопоточность
#
Base.Threads.@threads
— Macro
Threads.@threads [schedule] for ... end
Макрос, выполняющий цикл for
в параллельном режиме. Пространство итерации распределяется среди крупномодульных задач. Эту политику можно задать аргументом schedule
. Выполнение цикла ожидает оценки всех итераций.
См. также описание @spawn
и pmap
в Distributed
.
Расширенная справка
Семантика
За исключением случаев, когда более твердые гарантии определены параметром настройки расписания, цикл, выполняемый макросом @threads
, имеет следующую семантику.
Макрос @threads
выполняет тело цикла в неустановленном порядке и, возможно, в многопоточном режиме. Он не задает точные присваивания заданий и потоки рабочей роли. Для каждого выполнения присваивания могут отличаться. Код тела цикла (включая весь код, транзитивно вызываемый из него) не должен делать допущения относительно распределения итераций по заданиям или рабочим потокам, в котором они выполняются. Тело цикла для каждой итерации должно иметь возможность развиваться прогрессивно независимо от остальных итераций, и гонка по данным должна быть исключена. Таким образом, недопустимые синхронизации в различных итерациях могут взаимоблокироваться, а несинхронизированные сеансы доступа к памяти — привести к неопределенному поведению.
Например, приведенные выше условия предполагают следующее:
-
Блокировка, установленная в итерации, должна быть снята в пределах той же итерации.
-
Взаимодействие между итерациями с использованием примитивов, таких как
Channel
, является некорректным. -
Запись выполняется только в расположения, которые не используются совместно различными итерациями (за исключением случаев, когда используется блокировка или атомарная операция).
-
Если не используется расписание
:static
, значениеthreadid()
может изменяться даже в пределах одной итерации. См. разделTask Migration
.
Планировщики
Без аргумента планировщика точное создание расписания настроить нельзя, и оно может варьироваться в различных выпусках Julia. В настоящее время используется :dynamic
, если планировщик не задан.
Совместимость: Julia 1.5
Аргумент |
:dynamic
(по умолчанию)
Планировщик :dynamic
исполняет итерации динамически для доступных рабочих потоков. Текущая реализация предполагает, что рабочая нагрузка для каждой итерации является однородной. Однако в будущем такое допущение может перестать действовать.
Этот параметр планирования представляет собой просто подсказку для базового механизма выполнения. Однако при этом можно ожидать ряда свойства. Количество Task
, используемых планировщиком :dynamic
, ограничено небольшой постоянной величиной, равной количеству доступных рабочих потоков (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 миллисекунд).
Совместимость: Julia 1.8
Доступен параметр |
:greedy
Планировщик :greedy
порождает до Threads.threadpoolsize()
задач, каждая из которых обрабатывает как можно больше заданных итерируемых значений по мере их создания. Как только задача завершает работу, она берет следующее значение из итератора. Любая отдельная задача не обязательно выполняет работу со смежными значениями итератора. Итератор может выдавать значения вечно; требуется только интерфейс итератора (без индексации).
Этот вариант планирования обычно хорошо подходит, если рабочая нагрузка отдельных итераций неравномерна или имеет большой разброс.
Совместимость: Julia 1.11
Значение |
:static
Планировщик :static
создает одну задачу на поток и разделяет итерации поровну между ними, присваивая каждую конкретную задачу отдельно каждому потоку. В частности, значение threadid()
гарантированно будет константой в пределах одной итерации. Указание :static
является ошибочным, если используется из другого цикла @threads
или потока, отличного от 1.
Планирование |
Примеры
Чтобы продемонстрировать различные стратегии планирования, можно рассмотреть следующую функцию 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-секундные итерации, чтобы завершить цикл.
#
Base.Threads.foreach
— Function
Threads.foreach(f, channel::Channel;
schedule::Threads.AbstractSchedule=Threads.FairSchedule(),
ntasks=Threads.threadpoolsize())
Аналогично foreach(f, channel)
, но итерация по channel
и вызовы f
разделяются по ntasks
задачам, порождаемым Threads.@spawn
. Эта функция будет ожидать завершения внутренне порожденных задач, прежде чем вернуть управление.
Если 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
Для этой функции требуется версия Julia не ниже 1.6. |
#
Base.Threads.@spawn
— Macro
Threads.@spawn [:default|:interactive] expr
Создает Task
и применяет к нему schedule
для выполнения в любом доступном потоке в указанном пуле потоков (:default
, если не указано). Задача выделяется в поток после того, как станет доступной. Чтобы дождаться завершения задачи, вызовите wait
для результата этого макроса или вызовите fetch
, чтобы дождаться завершения и затем получить возвращаемое значение.
Значения можно интерполировать в @spawn
с помощью $
, который копирует значение непосредственно в сконструированное базовое замыкание. Это позволяет вставлять значение переменной, изолируя асинхронный код от изменений значения переменной в текущей задаче.
Поток, в котором выполняется задача, может измениться, если задача завершится, поэтому |
Совместимость: Julia 1.3
Этот макрос впервые реализован в Julia 1.3. |
Совместимость: Julia 1.4
Интерполяция значений с помощью |
Совместимость: Julia 1.9
Пул потоков можно указывать начиная с версии 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
— Function
Threads.threadid() -> Int
Получает идентификационный номер текущего потока выполнения. Главный поток имеет идентификатор 1
.
Примеры
julia> Threads.threadid()
1
julia> Threads.@threads for i in 1:4
println(Threads.threadid())
end
4
2
5
4
Поток, в котором выполняется задача, может измениться, если задача завершится, что называется |
#
Base.Threads.maxthreadid
— Function
Threads.maxthreadid() -> Int
Получает нижнюю границу числа доступных потоков (во всех пулах потоков), доступных процессу Julia, с семантикой atomic-acquire. Результат всегда будет больше или равен threadid()
, а также threadid(task)
для любой задачи, которую вы могли наблюдать до вызова maxthreadid
.
#
Base.Threads.nthreads
— Function
Threads.nthreads(:default | :interactive) -> Int
Получает текущее количество потоков в указанном пуле потоков. Потоки в :interactive
имеют идентификационные номера 1:nthreads(:interactive)
, а потоки в :default
— в диапазоне nthreads(:interactive) .+ (1:nthreads(:default))
.
См. также описание BLAS.get_num_threads
и BLAS.set_num_threads
в стандартной библиотеке LinearAlgebra
и nprocs()
в стандартной библиотеке Distributed
и Threads.maxthreadid()
.
#
Base.Threads.threadpool
— Function
Threads.threadpool(tid = threadid()) -> Symbol
Возвращает пул потоков указанного потока: :default
, :interactive
или :foreign
.
#
Base.Threads.nthreadpools
— Function
Threads.nthreadpools() -> Int
Возвращает количество текущих настроенных пулов потоков.
#
Base.Threads.threadpoolsize
— Function
Threads.threadpoolsize(pool::Symbol = :default) -> Int
Получает количество потоков, доступных пулу потоков по умолчанию (или указанному пулу потоков).
См. также описание BLAS.get_num_threads
и BLAS.set_num_threads
в стандартной библиотеке LinearAlgebra
и nprocs()
в стандартной библиотеке Distributed
.
#
Base.Threads.ngcthreads
— Function
Threads.ngcthreads() -> Int
Возвращает количество текущих настроенных потоков сборщика мусора. Сюда входят как потоки маркировки, так и параллельные потоки очистки.
См. также раздел Многопоточность.
Атомарные операции
#
atomic
— Keyword
Небезопасные операции с указателями совместимы с загрузкой и сохранением указателей, объявленных с типом _Atomic
и std::atomic
в C11 и C++23 соответственно. Если атомарная загрузка типа 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)
.
Дополнительные сведения см. в разделе Атомарные операции в каждом поле руководства.
Примеры
julia> mutable struct Atomic{T}; @atomic x::T; end
julia> a = Atomic(1)
Atomic{Int64}(1)
julia> @atomic a.x # получает поле х объекта а с последовательной согласованностью
1
julia> @atomic :sequentially_consistent a.x = 2 # задает поле х объекта а с последовательной согласованностью
2
julia> @atomic a.x += 1 # пошагово увеличивает поле х объекта а с последовательной согласованностью
3
julia> @atomic a.x + 1 # пошагово увеличивает поле х объекта а с последовательной согласованностью
3 => 4
julia> @atomic a.x # получает поле х объекта а с последовательной согласованностью
4
julia> @atomic max(a.x, 10) # изменяет поле х объекта а на максимальное значение с последовательной согласованностью
4 => 10
julia> @atomic a.x max 5 # снова изменяет поле х объекта а на максимальное значение с последовательной согласованностью
10 => 10
Совместимость: Julia 1.7
Для этой функции требуется версия 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)
.
Дополнительные сведения см. в разделе Атомарные операции в каждом поле руководства.
Примеры
julia> mutable struct Atomic{T}; @atomic x::T; end
julia> a = Atomic(1)
Atomic{Int64}(1)
julia> @atomicswap a.x = 2+2 # заменяет поле х объекта а на значение 4 с последовательной согласованностью
1
julia> @atomic a.x # получает поле х объекта а с последовательной согласованностью
4
Совместимость: Julia 1.7
Для этой функции требуется версия 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)
.
Дополнительные сведения см. в разделе Атомарные операции в каждом поле руководства.
Примеры
julia> mutable struct Atomic{T}; @atomic x::T; end
julia> a = Atomic(1)
Atomic{Int64}(1)
julia> @atomicreplace a.x 1 => 2 # заменяет поле х объекта а на значение 2, если оно было равно 1, с последовательной согласованностью
(old = 1, success = true)
julia> @atomic a.x # получает поле х объекта а с последовательной согласованностью
2
julia> @atomicreplace a.x 1 => 2 # заменяет поле х объекта а на значение 2, если оно было равно 1, с последовательной согласованностью
(old = 2, success = false)
julia> xchg = 2 => 0; # заменяет поле х объекта а на значение 0, если оно было равно 2, с последовательной согласованностью
julia> @atomicreplace a.x xchg
(old = 2, success = true)
julia> @atomic a.x # получает поле х объекта а с последовательной согласованностью
0
Совместимость: Julia 1.7
Для этой функции требуется версия 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)
.
Дополнительные сведения см. в разделе Атомарные операции в каждом поле руководства.
Примеры
julia> mutable struct AtomicOnce
@atomic x
AtomicOnce() = new()
end
julia> a = AtomicOnce()
AtomicOnce(#undef)
julia> @atomiconce a.x = 1 # присваивает полю х объекта а значение 1, если оно не было задано, с последовательной согласованностью
true
julia> @atomic a.x # получает поле х объекта а с последовательной согласованностью
1
julia> @atomiconce a.x = 1 # присваивает полю х объекта а значение 1, если оно не было задано, с последовательной согласованностью
false
Совместимость: Julia 1.11
Для этой функции требуется версия Julia не ниже 1.11. |
#
Core.AtomicMemory
— Type
AtomicMemory{T} == GenericMemory{:atomic, T, Core.CPU}
Вектор DenseVector{T}
фиксированного размера. Доступ к любому его элементу осуществляется атомарно (с упорядочением :monotonic
). Задание любого элемента должно осуществляться с помощью макроса @atomic
с явным указанием упорядочения.
Каждый элемент является независимо атомарным при доступе и не может быть задан неатомарно. В настоящее время макрос |
Подробные сведения см. в разделе Атомарные операции.
Совместимость: Julia 1.11
Для этого типа требуется версия Julia не ниже 1.11. |
Для небезопасного (unsafe
) набора функций также существуют необязательные параметры упорядочения памяти, которые выбирают совместимые с C/C++ версии этих атомарных операций, если для этого параметра задано unsafe_load
, unsafe_store!
, unsafe_swap!
, unsafe_replace!
и unsafe_modify!
.
Следующие интерфейсы API устарели, хотя, скорее всего, будут поддерживаться еще в нескольких выпусках. |
#
Base.Threads.Atomic
— Type
Threads.Atomic{T}
Содержит ссылку на объект типа T
, разрешая только атомарный доступ, то есть потокобезопасным способом.
Только определенные «простые» типы можно использовать атомарно, в частности примитивный логический тип, целочисленный тип и тип с плавающей запятой. К ним относятся Bool
, Int8
…Int128
, UInt8
…UInt128
и Float16
…Float64
.
Новые атомарные объекты можно создавать на основе неатомарных значений; если они не заданы, атомарный объект инициализируется с нулевым значением.
Доступ к атомарным объектам можно осуществлять с помощью нотации []
:
Примеры
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!
— Function
Threads.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!
— Function
Threads.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!
— Function
Threads.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!
— Function
Threads.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!
— Function
Threads.atomic_and!(x::Atomic{T}, val::T) where T
Атомарно выполняет битовую операцию «и» с x
и val
.
Выполняет 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!
— Function
Threads.atomic_nand!(x::Atomic{T}, val::T) where T
Атомарно выполняет битовую операцию «и не» с 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!
— Function
Threads.atomic_or!(x::Atomic{T}, val::T) where T
Атомарно выполняет битовую операцию «или» с x
и val
.
Выполняет 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!
— Function
Threads.atomic_xor!(x::Atomic{T}, val::T) where T
Атомарно выполняет битовую операцию исключающего «или» с x
и val
Выполняет 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!
— Function
Threads.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!
— Function
Threads.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
— Function
Threads.atomic_fence()
Вставляет последовательно согласованный барьер памяти.
Вставляет барьер памяти с последовательно согласованной семантикой упорядочивания. При необходимости доступны алгоритмы, то есть в тех случаях, когда упорядочивание получения/выпуска является недостаточным.
Это, скорее всего, весьма дорогая операция. Учитывая, что все остальные атомарные операции в Julia уже располагают семантикой получения/выпуска, явные барьеры в большинстве случаев не требуются.
Дополнительные сведения см. в инструкции LLVM fence
.
Вызов ccall с использованием пула потоков libuv (экспериментальная функция)
#
Base.@threadcall
— Macro
@threadcall((cfunc, clib), rettype, (argtypes...), argvals...)
Макрос @threadcall
вызывается так же, как и ccall
, но выполняет свои задачи в другом потоке. Рекомендуется использовать, если требуется вызвать блокирующую функцию С без блокирования текущего потока julia
. Многопоточный режим ограничивается размером пула потоков libuv, который по умолчанию включает 4 потока, но этот лимит можно увеличить, задав переменную среды UV_THREADPOOL_SIZE
и перезапустив процесс julia
.
Обратите внимание, что вызванная функция не должна выполнять обратный вызов к Julia.
Примитивы низкоуровневой синхронизации
Эти стандартные блоки используются для создания объектов регулярной синхронизации.
#
Base.Threads.SpinLock
— Type
SpinLock()
Создает нереентерабельную активную блокировку «проверить — проверить — установить». Рекурсивное использование приводит к взаимоблокировке. Такой тип блокировки можно использовать только для кода, выполнение которого не отнимает много времени и который не блокируется (например, при выполнении ввода-вывода). Как правило, вместо этого можно использовать ReentrantLock
.
Каждый lock
должен сопровождаться unlock
. Если !islocked(lck::SpinLock)
удерживается, trylock(lck)
выполняется успешно, если только нет других задач, пытающихся удержать блокировку в то же самое время.
Активные блокировки «проверить — проверить — установить» являются самыми быстрыми при наличии примерно 30 конкурирующих потоков. Если конкурирующих потоков больше, следует выбрать другие методы синхронизации.