Задачи
Core.TaskТип
Task(func)
Создайте Task (т.е. сопрограмму) для выполнения заданной функции func (которая должна быть вызываемой без аргументов). Задача завершается, когда эта функция возвращает значение. Задача будет выполняться в «возрасте мира» родительского элемента при создании, когда schedule.
По умолчанию задачи будут иметь установленный флаг «липкая» в значение true t.sticky. Это моделирует историческое значение по умолчанию для @async. Липкие задачи могут выполняться только на рабочем потоке, на котором они были первоначально запланированы, и при планировании они делают задачу, от которой они были запланированы, липкой. Чтобы получить поведение Threads.@spawn, вручную установите флаг «липкая» в false.
Примеры
julia> a() = sum(i for i in 1:1000); julia> b = Task(a);
В этом примере b является выполнимой Task задачей, которая еще не началась.
Base.@taskМакрос
@task
Оборачивает выражение в Task, не выполняя его, и возвращает Task. Это только создает задачу, а не запускает ее.
По умолчанию задачи будут иметь установленный флаг «липкая» в значение true t.sticky. Это моделирует историческое значение по умолчанию для @async. Липкие задачи могут выполняться только на рабочем потоке, на котором они были первоначально запланированы, и при планировании они делают задачу, от которой они были запланированы, липкой. Чтобы получить поведение Threads.@spawn, вручную установите флаг «липкая» в false.
Примеры
julia> a1() = sum(i for i in 1:1000); julia> b = @task a1(); julia> istaskstarted(b) false julia> schedule(b); julia> yield(); julia> istaskdone(b) trueисходный код
Base.@asyncМакрос
@async
Оборачивает выражение в Task и добавляет его в очередь планировщика локальной машины.
Значения могут быть интерполированы в @async с помощью $, которая копирует значение непосредственно в созданный базовый закрытый метод. Это позволяет вставлять значение переменной, изолируя асинхронный код от изменений значения переменной в текущей задаче.
Сильно рекомендуется всегда отдавать предпочтение Threads.@spawn перед @async, даже когда не требуется параллелизм, особенно в общедоступных распределенных библиотеках. Это связано с тем, что использование @async отключает миграцию задачи родителя через рабочие потоки в текущей реализации Julia. Таким образом, кажущееся безобидное использование @async в функции библиотеки может значительно повлиять на производительность очень разных частей приложений пользователя.
Интерполяция значений с помощью $ доступна начиная с Julia 1.4.
Base.asyncmapФункция
asyncmap(f, c...; ntasks=0, batch_size=nothing)
Использует несколько одновременных задач для отображения f по коллекции (или нескольким коллекциям равной длины). При нескольких аргументах коллекции f применяется поэлементно.
ntasks указывает количество задач для одновременного выполнения. В зависимости от длины коллекций, если ntasks не указано, для одновременного отображения будет использоваться до 100 задач.
ntasks также может быть задано как функция без аргументов. В этом случае количество задач, выполняемых параллельно, проверяется перед обработкой каждого элемента, и новая задача запускается, если значение ntasks_func больше текущего количества задач.
Если batch_size указано, коллекция обрабатывается в пакетном режиме. f должна быть функцией, которая должна принимать Vector кортежей аргументов и возвращать вектор результатов. Вектор входных данных будет иметь длину batch_size или меньше.
Следующие примеры показывают выполнение в разных задачах, возвращая objectid задач, в которых выполняется функция отображения.
Сначала, при неопределённом значении ntasks, каждый элемент обрабатывается в отдельной задаче.
julia> tskoid() = objectid(current_task());
julia> asyncmap(x->tskoid(), 1:5)
5-element Array{UInt64,1}:
0x6e15e66c75c75853
0x440f8819a1baa682
0x9fb3eeadd0c83985
0xebd3e35fe90d4050
0x29efc93edce2b961
julia> length(unique(asyncmap(x->tskoid(), 1:5)))
5
Со значением ntasks=2 все элементы обрабатываются в 2 задачах.
julia> asyncmap(x->tskoid(), 1:5; ntasks=2)
5-element Array{UInt64,1}:
0x027ab1680df7ae94
0xa23d2f80cd7cf157
0x027ab1680df7ae94
0xa23d2f80cd7cf157
0x027ab1680df7ae94
julia> length(unique(asyncmap(x->tskoid(), 1:5; ntasks=2)))
2
При определённом значении batch_size, функция отображения должна быть изменена на прием массива кортежей аргументов и возвращение массива результатов. map используется в изменённой функции отображения для достижения этого.
julia> batch_func(input) = map(x->string("args_tuple: ", x, ", element_val: ", x[1], ", task: ", tskoid()), input)
batch_func (generic function with 1 method)
julia> asyncmap(batch_func, 1:5; ntasks=2, batch_size=2)
5-element Array{String,1}:
"args_tuple: (1,), element_val: 1, task: 9118321258196414413"
"args_tuple: (2,), element_val: 2, task: 4904288162898683522"
"args_tuple: (3,), element_val: 3, task: 9118321258196414413"
"args_tuple: (4,), element_val: 4, task: 4904288162898683522"
"args_tuple: (5,), element_val: 5, task: 9118321258196414413"
исходный код
Base.asyncmap!Функция
asyncmap!(f, results, c...; ntasks=0, batch_size=nothing)
Подобно asyncmap, но хранит выходные данные в results вместо возврата коллекции.
Поведение может быть непредсказуемым, когда любая изменённая переменная разделяет память с любой другой переменной.
Base.current_taskФункция
current_task()
Получить текущую выполняющуюся Task.
Base.istaskdoneФункция
istaskdone(t::Task) -> Bool
Определить, завершилась ли задача.
Примеры
julia> a2() = sum(i for i in 1:1000); julia> b = Task(a2); julia> istaskdone(b) false julia> schedule(b); julia> yield(); julia> istaskdone(b) trueисходный код
Base.istaskstartedФункция
istaskstarted(t::Task) -> Bool
Определить, начала ли выполняться задача.
Примеры
julia> a3() = sum(i for i in 1:1000); julia> b = Task(a3); julia> istaskstarted(b) falseисходный код
Base.istaskfailedФункция
istaskfailed(t::Task) -> Bool
Определить, завершилась ли задача из-за возникновения исключения.
Примеры
julia> a4() = error("task failed");
julia> b = Task(a4);
julia> istaskfailed(b)
false
julia> schedule(b);
julia> yield();
julia> istaskfailed(b)
true
Для этой функции требуется по крайней мере Julia 1.3.
Base.task_local_storageМетод
task_local_storage(key)
Искать значение ключа в локальном хранилище текущей задачи.
исходный код
Base.task_local_storageМетод
task_local_storage(key, value)
Присвоить значение ключу в локальном хранилище текущей задачи.
исходный код
Base.task_local_storageМетод
task_local_storage(body, key, value)
Вызвать функцию body с изменённым локальным хранилищем задачи, в котором value назначено key; предыдущее значение key, или его отсутствие, восстанавливается после этого. Полезно для эмуляции динамического охвата.
Планирование
Base.yieldФункция
yield()
Переключение на планировщик, чтобы позволить выполнение другой запланированной задачи. Задача, которая вызывает эту функцию, все еще может быть выполнена и будет немедленно перезапущена, если нет других готовых задач.
исходный кодyield(t::Task, arg = nothing)
Быстрая, нечестная версия планирования schedule(t, arg); yield(), которая немедленно уступает t перед вызовом планировщика.
Base.yieldtoФункция
yieldto(t::Task, arg = nothing)
Переключение на заданную задачу. При первом переключении на задачу, функция задачи вызывается без аргументов. При последующих переключениях, arg возвращается из последнего вызова задачи к yieldto. Это вызов низкого уровня, который только переключает задачи, не учитывая состояния или планирование каким-либо образом. Его использование не рекомендуется.
Base.sleepФункция
sleep(seconds)
Заблокировать текущую задачу на указанное количество секунд. Минимальное время ожидания — 1 миллисекунда или значение 0.001.
Base.scheduleФункция
schedule(t::Task, [val]; error=false)
Добавить Task в очередь планировщика. Это заставляет задачу постоянно выполняться, когда система в противном случае простаивает, если задача не выполняет блокирующую операцию, например, wait.
Если указан второй аргумент val, он будет передан задаче (через возвращаемое значение yieldto) при её повторном выполнении. Если error имеет значение true, это значение будет поднято как исключение в разбуженной задаче.
Неправильно использовать schedule с произвольной Task, которая уже запущена. Подробности см. в справочнике API.
По умолчанию для задач установлен флаг sticky со значением true t.sticky. Это соответствует историческому значению по умолчанию для @async. Sticky-задачи могут выполняться только на том рабочем потоке, на котором они были впервые запланированы, и при планировании делают задачу, из которой они планировались, sticky. Чтобы получить поведение Threads.@spawn, вручную установите флаг sticky в значение false.
Примеры
julia> a5() = sum(i for i in 1:1000); julia> b = Task(a5); julia> istaskstarted(b) false julia> schedule(b); julia> yield(); julia> istaskstarted(b) true julia> istaskdone(b) trueисходный код
Синхронизация
Base.errormonitorФункция
errormonitor(t::Task)
Вывести журнал ошибок в stderr, если задача t завершится с ошибкой.
Примеры
julia> Base._wait(errormonitor(Threads.@spawn error("task failed")))
Unhandled Task ERROR: task failed
Stacktrace:
[...]
исходный код
Base.@syncМакрос
@sync
Подождать, пока все лексически вложенные вызовы @async, @spawn, Distributed.@spawnat и Distributed.@distributed завершатся. Все исключения, сгенерированные вложенными асинхронными операциями, будут собраны и сгенерированы как CompositeException.
Примеры
julia> Threads.nthreads()
4
julia> @sync begin
Threads.@spawn println("Thread-id $(Threads.threadid()), task 1")
Threads.@spawn println("Thread-id $(Threads.threadid()), task 2")
end;
Thread-id 3, task 1
Thread-id 1, task 2
исходный код
Base.waitФункция
Примечание для Threads.Condition:
Вызывающая функция должна удерживать lock, владеющую Threads.Condition, перед вызовом этого метода. Вызывающая задача будет заблокирована, пока какая-то другая задача не разбудит её, обычно вызвав notify для того же объекта Threads.Condition. Блокировка будет атомарно освобождена во время блокировки (даже если она была заблокирована рекурсивно), и будет повторно захвачена перед возвратом.
wait(r::Future)
Дождаться появления значения для указанного Future.
wait(r::RemoteChannel, args...)
Дождаться появления значения на указанном RemoteChannel.
wait([x])
Заблокировать текущую задачу до наступления какого-либо события, в зависимости от типа аргумента:
Channel: Дождаться добавления значения в канал.Condition: Дождатьсяnotifyна условии и вернуть параметрval, переданный вnotify. Ожидание на условии также позволяет передатьfirst=true, что приводит к размещению ожидающего в начале очереди на пробуждение поnotify, вместо обычного поведения "первый пришёл - первый обслужен".Process: Подождать завершения процесса или цепочки процессов. Полеexitcodeпроцесса можно использовать для определения успеха или неудачи.Task: Подождать завершенияTask. Если задача завершится с ошибкой, будет выброшено исключениеTaskFailedException(которое оборачивает завершившуюся с ошибкой задачу).RawFD: Подождать изменений на дескрипторе файла (см. пакетFileWatching).
Если аргумент не указан, задача блокируется на неопределённое время. Задачу можно перезапустить только явно, вызвав schedule или yieldto.
Часто wait вызывается внутри цикла while, чтобы убедиться, что ожидаемое условие выполняется перед продолжением.
wait(c::Channel)
Блокируется, пока Channel не станет isready.
julia> c = Channel(1); julia> isready(c) false julia> task = Task(() -> wait(c)); julia> schedule(task); julia> istaskdone(task) # task is blocked because channel is not ready false julia> put!(c, 1); julia> istaskdone(task) # task is now unblocked trueисходный код
Base.fetchМетод
fetch(t::Task)
Подождать завершения Task, затем вернуть значение результата. Если задача завершится с ошибкой, будет выброшено исключение TaskFailedException (которое оборачивает завершившуюся с ошибкой задачу).
Base.fetchМетод
fetch(x::Any)
Вернуть x.
Base.timedwaitФункция
timedwait(testcb, timeout::Real; pollint::Real=0.1)
Подождать, пока testcb() вернёт true или пройдёт timeout секунд, в зависимости от того, что произойдёт раньше. Функция-тест вызывается каждые pollint секунды. Минимальное значение для pollint составляет 0,001 секунды, то есть 1 миллисекунда.
Возвращает :ok или :timed_out.
Примеры
julia> cb() = (sleep(5); return); julia> t = @async cb(); julia> timedwait(()->istaskdone(t), 1) :timed_out julia> timedwait(()->istaskdone(t), 6.5) :okисходный код
Base.ConditionТип
Condition()
Создать источник событий с триггером по краю, для которого задачи могут ожидать. Задачи, вызывающие wait на Condition, приостанавливаются и помещаются в очередь. Задачи возобновляются, когда позже вызывается notify для Condition. Ожидание на условии может вернуть значение или сгенерировать ошибку, если используются необязательные аргументы notify. Триггер по краю означает, что разбужены только задачи, ожидающие в момент вызова notify. Для триггеров по уровню нужно сохранять дополнительное состояние, чтобы отслеживать, произошло ли уведомление. Типы Channel и Threads.Event делают это и могут использоваться для событий с триггером по уровню.
Этот объект НЕ потокобезопасен. См. Threads.Condition для потокобезопасной версии.
Base.Threads.ConditionТип
Threads.Condition([lock])
Потокобезопасная версия Base.Condition.
Чтобы вызвать wait или notify на Threads.Condition, необходимо сначала вызвать lock на нём. При вызове wait, блокировка атомарно освобождается во время ожидания, и будет повторно захвачена перед возвратом wait. Поэтому характерное использование Threads.Condition c выглядит следующим образом:
lock(c)
try
while !thing_we_are_waiting_for
wait(c)
end
finally
unlock(c)
end
Эта функция требует как минимум Julia 1.2.
Base.EventТип
Event([autoreset=false])
Создайте источник событий с уровнем срабатывания. Задачи, которые вызывают wait на Event приостанавливаются и помещаются в очередь до тех пор, пока не будет вызван notify на Event. После вызова notify, Event остаётся в сигнализированном состоянии, и задачи больше не будут блокироваться при ожидании его, пока не будет вызван reset.
Если autoreset имеет значение true, то не более одной задачи будет освобождена из wait для каждого вызова notify.
Это обеспечивает порядок памяти для приобретения и освобождения при уведомлении/ожидании.
Эта функциональность требует как минимум Julia 1.1.
Функциональность autoreset и гарантия порядка памяти требуют как минимум Julia 1.8.
Base.notifyФункция
notify(condition, val=nothing; all=true, error=false)
Разбудить задачи, ожидающие условия, передав им val. Если all имеет значение true (по умолчанию), все ожидающие задачи будятся, в противном случае только одна. Если error имеет значение true, переданное значение поднимается как исключение в разбуженных задачах.
Возвращает количество разбуженных задач. Возвращает 0, если на condition нет ожидающих задач.
Base.resetМетод
reset(::Event)
Сбросить Event обратно в неинициализированное состояние. Тогда любые последующие вызовы wait будут блокироваться до тех пор, пока снова не будет вызван notify.
Base.SemaphoreТип
Semaphore(sem_size)
Создайте счётчик семафора, который позволяет не более sem_size приобретений быть в использовании в любой момент. Каждое приобретение должно быть сопоставлено с освобождением.
Это обеспечивает порядок памяти для приобретения и освобождения при вызовах приобретения/освобождения.
исходный код
Base.acquireФункция
acquire(s::Semaphore)
Подождите, пока один из sem_size разрешений не станет доступным, блокируясь до тех пор, пока одно не будет приобретено.
acquire(f, s::Semaphore)
Выполните f после приобретения из семафора s, и release по завершении или ошибке.
Например, блок do, который гарантирует, что только 2 вызова foo будут активны одновременно:
s = Base.Semaphore(2)
@sync for _ in 1:100
Threads.@spawn begin
Base.acquire(s) do
foo()
end
end
end
Этот метод требует как минимум Julia 1.8.
Base.releaseФункция
release(s::Semaphore)
Возвращает одно разрешение в пул, возможно, позволяя другой задаче его приобрести и возобновить выполнение.
исходный код
Base.AbstractLockТип
AbstractLock
Абстрактный супертип, описывающий типы, которые реализуют примитивы синхронизации: lock, trylock, unlock и islocked.
Base.lockФункция
lock(lock)
Приобретает lock при его доступности. Если блокировка уже заблокирована другой задачей/потоком, дождитесь, пока она станет доступной.
Каждое lock должно быть сопоставлено с unlock.
lock(f::Function, lock)
Приобретает lock, выполняет f с захваченной lock, и освобождает lock при возвращении f. Если блокировка уже заблокирована другой задачей/потоком, дождитесь, пока она станет доступной.
При возвращении этой функции lock была освобождена, поэтому вызывающая сторона не должна пытаться unlock её.
См. также: @lock.
Использование Channel в качестве второго аргумента требует Julia 1.7 или более поздней версии.
lock(f::Function, l::Lockable)
Приобретает блокировку, связанную с l, выполняет f с захваченной блокировкой и освобождает блокировку при возвращении f. f получит один позиционный аргумент: значение, обернутое l. Если блокировка уже заблокирована другой задачей/потоком, дождитесь, пока она станет доступной. При возвращении этой функции lock была освобождена, поэтому вызывающая сторона не должна пытаться unlock её.
Требуется как минимум Julia 1.11.
Base.unlockФункция
unlock(lock)
Освобождает владение lock.
Если это рекурсивная блокировка, которая была приобретена ранее, уменьшается внутренний счётчик и возвращается немедленно.
исходный код
Base.trylockФункция
trylock(lock) -> Success (Boolean)
Приобрести блокировку, если она доступна, и вернуть true в случае успеха. Если блокировка уже заблокирована другой задачей/потоком, вернуть false.
Каждое успешное trylock должно быть сопоставлено с unlock.
Функция trylock в сочетании с islocked может использоваться для написания алгоритмов «проверка и установка» или экспоненциального отката, если это поддерживается typeof(lock) (прочитайте документацию).
Base.islockedФункция
islocked(lock) -> Status (Boolean)
Проверить, удерживается ли lock какой-либо задачей/потоком. Эта функция сама по себе не должна использоваться для синхронизации. Однако, islocked в сочетании с trylock может использоваться для написания алгоритмов «проверка и установка» или экспоненциального отката, если это поддерживается typeof(lock) (прочитайте документацию).
Дополнительная помощь
Например, экспоненциальный откат может быть реализован следующим образом, если реализация lock удовлетворяет свойствам, описанным ниже.
nspins = 0
while true
while islocked(lock)
GC.safepoint()
nspins += 1
nspins > LIMIT && error("timeout")
end
trylock(lock) && break
backoff()
end
Реализация
Рекомендуется, чтобы реализация блокировки определяла islocked со следующими свойствами и указанием об этом в своей документации.
-
islocked(lock)является свободной от гонок данных. - Если
islocked(lock)возвращаетfalse, непосредственный вызовtrylock(lock)должен быть успешным (возвращаетtrue) в случае отсутствия помех от других задач.
Base.ReentrantLockТип
ReentrantLock()
Создаёт рекурсивную блокировку для синхронизации Task. Одна и та же задача может приобретать блокировку сколько угодно раз (отсюда и «Reentrant» в названии). Каждое lock должно быть сопоставлено с unlock.
Вызов lock также запретит выполнение финализаторов в этом потоке до соответствующего unlock. Использование стандартного шаблона блокировки, проиллюстрированного ниже, должно поддерживаться, но будьте осторожны при инвертировании порядка try/lock или пропуска блоков try (например, попытка возврата с ещё удерживаемой блокировкой):
Это обеспечивает порядок памяти для приобретения/освобождения при вызовах lock/unlock.
lock(l)
try
<atomic work>
finally
unlock(l)
end
Если !islocked(lck::ReentrantLock) выполняется, trylock(lck) имеет успех, если нет других задач, пытающихся удерживать блокировку «одновременно».
Base.@lockМакрос
@lock l expr
Макроверсия lock(f, l::AbstractLock), но с функцией expr вместо f. Развертывается в:
lock(l)
try
expr
finally
unlock(l)
end
Это аналогично использованию lock с блоком do, но избегает создания замыкания, что может улучшить производительность.
@lock была добавлена в Julia 1.3 и экспортирована в Julia 1.10.
Base.LockableТип
Lockable(value, lock = ReentrantLock())
Создаёт объект Lockable, который оборачивает value и связывает его с предоставленным lock. Этот объект поддерживает @lock, lock, trylock, unlock. Для доступа к значению необходимо использовать индексацию объекта Lockable, удерживая замок.
Требуется как минимум Julia 1.11.
Пример
julia> locked_list = Base.Lockable(Int[]);
julia> @lock(locked_list, push!(locked_list[], 1)) # must hold the lock to access the value
1-element Vector{Int64}:
1
julia> lock(summary, locked_list)
"1-element Vector{Int64}"
исходный кодКаналы
Base.AbstractChannelТип
AbstractChannel{T}
Представление канала, передающего объекты типа T.
Base.ChannelТип
Channel{T=Any}(size::Int=0)
Создаёт канал Channel с внутренней буфером, который может содержать максимальное количество size объектов типа T. Вызовы put! на полном канале будут блокироваться до тех пор, пока объект не будет удалён с помощью take!.
Channel(0) создаёт небуферизованный канал. put! блокируется до тех пор, пока не будет вызван соответствующий take!. И наоборот.
Другие конструкторы:
-
Channel(): конструктор по умолчанию, эквивалентныйChannel{Any}(0) -
Channel(Inf): эквивалентныйChannel{Any}(typemax(Int)) -
Channel(sz): эквивалентныйChannel{Any}(sz)
Конструктор по умолчанию Channel() и по умолчанию size=0 были добавлены в Julia 1.3.
Base.ChannelМетод
Channel{T=Any}(func::Function, size=0; taskref=nothing, spawn=false, threadpool=nothing)
Создаёт новую задачу из func, связывает её с новым каналом типа T и размером size, и планирует выполнение задачи, всё в одном вызове. Канал автоматически закрывается при завершении задачи.
func должен принимать привязанный канал в качестве единственного аргумента.
Если вам нужна ссылка на созданную задачу, передайте объект Ref{Task} через ключевой аргумент taskref.
Если spawn=true, задача, созданная для Task может быть запланирована на другой потоке параллельно, что эквивалентно созданию задачи через Threads.@spawn.
Если spawn=true и аргумент threadpool не задан, он по умолчанию равен :default.
Если аргумент threadpool задан (в :default или :interactive), это означает, что spawn=true и новая задача запускаются в указанном пуле потоков.
Возвращает Channel.
Примеры
julia> chnl = Channel() do ch
foreach(i -> put!(ch, i), 1:4)
end;
julia> typeof(chnl)
Channel{Any}
julia> for i in chnl
@show i
end;
i = 1
i = 2
i = 3
i = 4
Ссылка на созданную задачу:
julia> taskref = Ref{Task}();
julia> chnl = Channel(taskref=taskref) do ch
println(take!(ch))
end;
julia> istaskdone(taskref[])
false
julia> put!(chnl, "Hello");
Hello
julia> istaskdone(taskref[])
true
Параметр spawn= был добавлен в Julia 1.3. Этот конструктор был добавлен в Julia 1.3. В более ранних версиях Julia, Channel использовал ключевые аргументы для установки size и T, но эти конструкторы устарели.
Аргумент threadpool= был добавлен в Julia 1.9.
julia> chnl = Channel{Char}(1, spawn=true) do ch
for c in "hello world"
put!(ch, c)
end
end
Channel{Char}(1) (2 items available)
julia> String(collect(chnl))
"hello world"
исходный код
Base.put!Метод
put!(c::Channel, v)
Добавляет элемент v в канал c. Блокируется, если канал заполнен.
Для небуферизованных каналов блокируется до тех пор, пока другая задача не выполнит take!.
v теперь преобразуется в тип канала с помощью convert при вызове put!.
Base.take!Метод
take!(c::Channel)
Удаляет и возвращает значение из канала Channel в порядке очереди. Блокируется до тех пор, пока данные не станут доступными. Для небуферизованных каналов блокируется до тех пор, пока другая задача не выполнит put!.
Примеры
Буферизованный канал:
julia> c = Channel(1); julia> put!(c, 1); julia> take!(c) 1
Небуферизованный канал:
julia> c = Channel(0); julia> task = Task(() -> put!(c, 1)); julia> schedule(task); julia> take!(c) 1исходный код
Base.isreadyМетод
isready(c::Channel)
Определяет, есть ли значение в Channel. Возвращает значение немедленно, не блокируется.
Для небуферизованных каналов возвращает true если есть задачи, ожидающие put!.
Примеры
Буферизованный канал:
julia> c = Channel(1); julia> isready(c) false julia> put!(c, 1); julia> isready(c) true
Небуферизованный канал:
julia> c = Channel(); julia> isready(c) # no tasks waiting to put! false julia> task = Task(() -> put!(c, 1)); julia> schedule(task); # schedule a put! task julia> isready(c) trueисходный код
Base.fetchМетод
fetch(c::Channel)
Ожидает и возвращает (без удаления) первый доступный элемент из Channel. Примечание: fetch не поддерживается для небуферизованного (0-размерного) Channel.
Примеры
Буферизованный канал:
julia> c = Channel(3) do ch
foreach(i -> put!(ch, i), 1:3)
end;
julia> fetch(c)
1
julia> collect(c) # item is not removed
3-element Vector{Any}:
1
2
3
исходный код
Base.closeМетод
close(c::Channel[, excp::Exception])
Закрывает канал. Исключение (если указано в excp):
Base.bindМетод
bind(chnl::Channel, task::Task)
Связывает срок жизни chnl с задачей. Channel chnl автоматически закрывается при завершении задачи. Любое необработанное исключение в задаче передаётся всем ожидающим задачам на chnl.
Объект канала можно явным образом закрыть независимо от завершения задачи. Завершение задач не влияет на уже закрытые объекты каналов Channel.
Когда канал связан с несколькими задачами, первая завершающаяся задача закрывает канал. Когда несколько каналов связаны с одной задачей, завершение задачи закрывает все связанные каналы.
Примеры
julia> c = Channel(0);
julia> task = @async foreach(i->put!(c, i), 1:4);
julia> bind(c,task);
julia> for i in c
@show i
end;
i = 1
i = 2
i = 3
i = 4
julia> isopen(c)
false
julia> c = Channel(0);
julia> task = @async (put!(c, 1); error("foo"));
julia> bind(c, task);
julia> take!(c)
1
julia> put!(c, 1);
ERROR: TaskFailedException
Stacktrace:
[...]
nested task error: foo
[...]
исходный код
Низкоуровневая синхронизация с использованием schedule и wait
Самый простой корректный способ использования schedule — это на канале, который ещё не запущен (не запланирован). Однако, можно использовать schedule и wait как очень низкоуровневый строительный блок для создания интерфейсов синхронизации. Ключевое условие для вызова schedule(task) заключается в том, что вызывающий должен "владеть" task; т.е. он должен знать, что вызов wait в данном task происходит в местах, известных коду, вызывающему schedule(task). Одним из способов обеспечения такого условия является использование атомарных операций, как показано в следующем примере:
@enum OWEState begin
OWE_EMPTY
OWE_WAITING
OWE_NOTIFYING
end
mutable struct OneWayEvent
@atomic state::OWEState
task::Task
OneWayEvent() = new(OWE_EMPTY)
end
function Base.notify(ev::OneWayEvent)
state = @atomic ev.state
while state !== OWE_NOTIFYING
# Spin until we successfully update the state to OWE_NOTIFYING:
state, ok = @atomicreplace(ev.state, state => OWE_NOTIFYING)
if ok
if state == OWE_WAITING
# OWE_WAITING -> OWE_NOTIFYING transition means that the waiter task is
# already waiting or about to call `wait`. The notifier task must wake up
# the waiter task.
schedule(ev.task)
else
@assert state == OWE_EMPTY
# Since we are assuming that there is only one notifier task (for
# simplicity), we know that the other possible case here is OWE_EMPTY.
# We do not need to do anything because we know that the waiter task has
# not called `wait(ev::OneWayEvent)` yet.
end
break
end
end
return
end
function Base.wait(ev::OneWayEvent)
ev.task = current_task()
state, ok = @atomicreplace(ev.state, OWE_EMPTY => OWE_WAITING)
if ok
# OWE_EMPTY -> OWE_WAITING transition means that the notifier task is guaranteed to
# invoke OWE_WAITING -> OWE_NOTIFYING transition. The waiter task must call
# `wait()` immediately. In particular, it MUST NOT invoke any function that may
# yield to the scheduler at this point in code.
wait()
else
@assert state == OWE_NOTIFYING
# Otherwise, the `state` must have already been moved to OWE_NOTIFYING by the
# notifier task.
end
return
end
ev = OneWayEvent()
@sync begin
@async begin
wait(ev)
println("done")
end
println("notifying...")
notify(ev)
end
# output
notifying...
done
OneWayEvent позволяет одной задаче блокировать wait для другой задачи notify. Это ограниченный интерфейс связи, поскольку wait может использоваться только один раз из одной задачи (обратите внимание на неатомарную операцию присваивания ev.task).
В этом примере, notify(ev::OneWayEvent) разрешено вызывать schedule(ev.task) только в том случае, если оно изменяет состояние с OWE_WAITING на OWE_NOTIFYING. Это позволяет нам узнать, что задача, выполняющая wait(ev::OneWayEvent), теперь находится в ветке ok и что не может быть других задач, которые пытаются schedule(ev.task), поскольку их @atomicreplace(ev.state, state => OWE_NOTIFYING) потерпит неудачу.
© 2009–2024 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/v1.11/base/parallel/