From d541c2d36fb3996b66e9c8d581f0745721f0f01c Mon Sep 17 00:00:00 2001 From: Jeff Bezanson Date: Fri, 1 May 2020 13:54:00 -0400 Subject: [PATCH] upgrade threads from experimental to stable-with-caveats more threading doc edits --- NEWS.md | 5 +++- base/threadingconstructs.jl | 2 +- base/threads.jl | 2 +- doc/src/base/multi-threading.md | 24 ++++++++++++---- doc/src/base/parallel.md | 20 +++++++++----- doc/src/manual/asynchronous-programming.md | 14 +++++----- doc/src/manual/multi-threading.md | 32 ++++++++++++++++++++++ 7 files changed, 76 insertions(+), 23 deletions(-) diff --git a/NEWS.md b/NEWS.md index 253e239b09c95..ee9bc4fbc4714 100644 --- a/NEWS.md +++ b/NEWS.md @@ -104,11 +104,14 @@ Command-line option changes Multi-threading changes ----------------------- + +* Parts of the multi-threading API are now considered stable, with caveats. + This includes all documented identifiers from `Base.Threads` except the + `atomic_` operations. * `@threads` now allows an optional schedule argument. Use `@threads :static ...` to ensure that the same schedule will be used as in past versions; the default schedule is likely to change in the future. - Build system changes -------------------- * The build system now contains a pure-make caching system for expanding expensive operations at the latest diff --git a/base/threadingconstructs.jl b/base/threadingconstructs.jl index ad939e76b1742..aa122044a7881 100644 --- a/base/threadingconstructs.jl +++ b/base/threadingconstructs.jl @@ -155,7 +155,7 @@ constructed underlying closure. This allows you to insert the _value_ of a varia isolating the aysnchronous code from changes to the variable's value in the current task. !!! note - This feature is currently considered experimental. + See the manual chapter on threading for important caveats. !!! compat "Julia 1.3" This macro is available as of Julia 1.3. diff --git a/base/threads.jl b/base/threads.jl index 292513418525b..2b68c7104ee5e 100644 --- a/base/threads.jl +++ b/base/threads.jl @@ -1,7 +1,7 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license """ -Experimental multithreading support. +Multithreading support. """ module Threads diff --git a/doc/src/base/multi-threading.md b/doc/src/base/multi-threading.md index d5dfc88f333f6..71a693a2c619a 100644 --- a/doc/src/base/multi-threading.md +++ b/doc/src/base/multi-threading.md @@ -1,15 +1,27 @@ # [Multi-Threading](@id lib-multithreading) -This experimental interface supports Julia's multi-threading capabilities. Types and functions -described here might (and likely will) change in the future. - ```@docs -Base.Threads.threadid -Base.Threads.nthreads Base.Threads.@threads Base.Threads.@spawn +Base.Threads.threadid +Base.Threads.nthreads ``` +## Synchronization + +```@docs +Base.Threads.Condition +Base.Threads.Event +``` + +See also [Synchronization](@ref lib-task-sync). + +## Atomic operations + +!!! warning + + The API for atomic operations has not yet been finalized and is likely to change. + ```@docs Base.Threads.Atomic Base.Threads.atomic_cas! @@ -31,7 +43,7 @@ Base.Threads.atomic_fence Base.@threadcall ``` -# Low-level synchronization primitives +## Low-level synchronization primitives These building blocks are used to create the regular synchronization objects. diff --git a/doc/src/base/parallel.md b/doc/src/base/parallel.md index 453cd4562ba2c..ce8e25107ab51 100644 --- a/doc/src/base/parallel.md +++ b/doc/src/base/parallel.md @@ -4,10 +4,8 @@ Core.Task Base.@task Base.@async -Base.@sync Base.asyncmap Base.asyncmap! -Base.fetch(t::Task) Base.current_task Base.istaskdone Base.istaskstarted @@ -17,21 +15,25 @@ Base.task_local_storage(::Any, ::Any) Base.task_local_storage(::Function, ::Any, ::Any) ``` -# Scheduling +## Scheduling ```@docs Base.yield Base.yieldto Base.sleep +Base.schedule +``` + +## [Synchronization](@id lib-task-sync) + +```@docs +Base.@sync Base.wait +Base.fetch(t::Task) Base.timedwait Base.Condition -Base.Threads.Condition Base.notify -Base.schedule - -Base.Threads.Event Base.Semaphore Base.acquire @@ -43,7 +45,11 @@ Base.unlock Base.trylock Base.islocked Base.ReentrantLock +``` + +## Channels +```@docs Base.Channel Base.Channel(::Function) Base.put!(::Channel, ::Any) diff --git a/doc/src/manual/asynchronous-programming.md b/doc/src/manual/asynchronous-programming.md index 787fc0b061935..8589218472e96 100644 --- a/doc/src/manual/asynchronous-programming.md +++ b/doc/src/manual/asynchronous-programming.md @@ -10,7 +10,7 @@ This sort of scenario falls in the domain of asynchronous programming, sometimes also referred to as concurrent programming (since, conceptually, multiple things are happening at once). -To address these scenarios, Julia provides `Task`s (also known by several other +To address these scenarios, Julia provides [`Task`](@ref)s (also known by several other names, such as symmetric coroutines, lightweight threads, cooperative multitasking, or one-shot continuations). When a piece of computing work (in practice, executing a particular function) is designated as @@ -26,7 +26,7 @@ calls, where the called function must finish executing before control returns to You can think of a `Task` as a handle to a unit of computational work to be performed. It has a create-start-run-finish lifecycle. Tasks are created by calling the `Task` constructor on a 0-argument function to run, -or using the `@task` macro: +or using the [`@task`](@ref) macro: ``` julia> t = @task begin; sleep(5); println("done"); end @@ -36,7 +36,7 @@ Task (runnable) @0x00007f13a40c0eb0 `@task x` is equivalent to `Task(()->x)`. This task will wait for five seconds, and then print `done`. However, it has not -started running yet. We can run it whenever we're ready by calling `schedule`: +started running yet. We can run it whenever we're ready by calling [`schedule`](@ref): ``` julia> schedule(t); @@ -47,12 +47,12 @@ That is because it simply adds `t` to an internal queue of tasks to run. Then, the REPL will print the next prompt and wait for more input. Waiting for keyboard input provides an opportunity for other tasks to run, so at that point `t` will start. -`t` calls `sleep`, which sets a timer and stops execution. +`t` calls [`sleep`](@ref), which sets a timer and stops execution. If other tasks have been scheduled, they could run then. After five seconds, the timer fires and restarts `t`, and you will see `done` printed. `t` is then finished. -The `wait` function blocks the calling task until some other task finishes. +The [`wait`](@ref) function blocks the calling task until some other task finishes. So for example if you type ``` @@ -63,8 +63,8 @@ instead of only calling `schedule`, you will see a five second pause before the next input prompt appears. That is because the REPL is waiting for `t` to finish before proceeding. -It is common to want to create a task and schedule it right away, so a -macro called `@async` is provided for that purpose --- `@async x` is +It is common to want to create a task and schedule it right away, so the +macro [`@async`](@ref) is provided for that purpose --- `@async x` is equivalent to `schedule(@task x)`. ## Communicating with Channels diff --git a/doc/src/manual/multi-threading.md b/doc/src/manual/multi-threading.md index 69e2d83364564..dfdc931aeeb76 100644 --- a/doc/src/manual/multi-threading.md +++ b/doc/src/manual/multi-threading.md @@ -213,3 +213,35 @@ therefore a blocking call like other Julia APIs. It is very important that the called function does not call back into Julia, as it will segfault. `@threadcall` may be removed/changed in future versions of Julia. + +## Caveats + +At this time, most operations in the Julia runtime and standard libraries +can be used in a thread-safe manner, if the user code is data-race free. +However, in some areas work on stabilizing thread support is ongoing. +Multi-threaded programming has many inherent difficulties, and if a program +using threads exhibits unusual or undesirable behavior (e.g. crashes or +mysterious results), thread interactions should typically be suspected first. + +There are a few specific limitations and warnings to be aware of when using +threads in Julia: + + * Base collection types require manual locking if used simultaneously by + multiple threads where at least one thread modifies the collection + (common examples include `push!` on arrays, or inserting + items into a `Dict`). + * After a task starts running on a certain thread (e.g. via `@spawn`), it + will always be restarted on the same thread after blocking. In the future + this limitation will be removed, and tasks will migrate between threads. + * `@threads` currently uses a static schedule, using all threads and assigning + equal iteration counts to each. In the future the default schedule is likely + to change to be dynamic. + * The schedule used by `@spawn` is nondeterministic and should not be relied on. + * Compute-bound, non-memory-allocating tasks can prevent garbage collection from + running in other threads that are allocating memory. In these cases it may + be necessary to insert a manual call to `GC.safepoint()` to allow GC to run. + This limitation will be removed in the future. + * Avoid using finalizers in conjunction with threads, particularly if they + operate on objects shared by multiple threads. + * Avoid running top-level operations, e.g. `include`, or `eval` of type, + method, and module definitions in parallel.