Skip to content

Commit

Permalink
remove incorrect examples about thread-unsafe functions in docs (Juli…
Browse files Browse the repository at this point in the history
…aLang#33419)

* remove incorrect examples about thread unsafe functions

* Delete my_file.txt
  • Loading branch information
KristofferC committed Jan 8, 2020
1 parent 8a19ef8 commit 6088df6
Showing 1 changed file with 1 addition and 89 deletions.
90 changes: 1 addition & 89 deletions doc/src/manual/parallel-computing.md
Original file line number Diff line number Diff line change
Expand Up @@ -396,95 +396,7 @@ When using multi-threading we have to be careful when using functions that are n
For instance functions that have their
[name ending with `!`](https://docs.julialang.org/en/latest/manual/style-guide/#Append-!-to-names-of-functions-that-modify-their-arguments-1)
by convention modify their arguments and thus are not pure. However, there are
functions that have side effects and their name does not end with `!`. For
instance [`findfirst(regex, str)`](@ref) mutates its `regex` argument or
[`rand()`](@ref) changes `Base.GLOBAL_RNG` :

```julia-repl
julia> using Base.Threads
julia> nthreads()
4
julia> function f()
s = repeat(["123", "213", "231"], outer=1000)
x = similar(s, Int)
rx = r"1"
@threads for i in 1:3000
x[i] = findfirst(rx, s[i]).start
end
count(v -> v == 1, x)
end
f (generic function with 1 method)
julia> f() # the correct result is 1000
1017
julia> function g()
a = zeros(1000)
@threads for i in 1:1000
a[i] = rand()
end
length(unique(a))
end
g (generic function with 1 method)
julia> Random.seed!(1); g() # the result for a single thread is 1000
781
```

In such cases one should redesign the code to avoid the possibility of a race condition or use
[synchronization primitives](https://docs.julialang.org/en/latest/base/multi-threading/#Synchronization-Primitives-1).

For example in order to fix `findfirst` example above one needs to have a
separate copy of `rx` variable for each thread:

```julia-repl
julia> function f_fix()
s = repeat(["123", "213", "231"], outer=1000)
x = similar(s, Int)
rx = [Regex("1") for i in 1:nthreads()]
@threads for i in 1:3000
x[i] = findfirst(rx[threadid()], s[i]).start
end
count(v -> v == 1, x)
end
f_fix (generic function with 1 method)
julia> f_fix()
1000
```

We now use `Regex("1")` instead of `r"1"` to make sure that Julia
creates separate instances of `Regex` object for each entry of `rx` vector.

The case of `rand` is a bit more complex as we have to ensure that each thread
uses non-overlapping pseudorandom number sequences. This can be simply ensured
by using `Future.randjump` function:


```julia-repl
julia> using Random; import Future
julia> function g_fix(r)
a = zeros(1000)
@threads for i in 1:1000
a[i] = rand(r[threadid()])
end
length(unique(a))
end
g_fix (generic function with 1 method)
julia> r = let m = MersenneTwister(1)
[m; accumulate(Future.randjump, fill(big(10)^20, nthreads()-1), init=m)]
end;
julia> g_fix(r)
1000
```

We pass the `r` vector to `g_fix` as generating several RGNs is an expensive
operation so we do not want to repeat it every time we run the function.
functions that have side effects and their name does not end with `!`.

## @threadcall (Experimental)

Expand Down

0 comments on commit 6088df6

Please sign in to comment.