@@ -396,95 +396,7 @@ When using multi-threading we have to be careful when using functions that are n
396
396
For instance functions that have their
397
397
[name ending with ` !` ](https: // docs. julialang. org/ en/ latest/ manual/ style- guide/ # Append-!-to-names-of-functions-that-modify-their-arguments-1)
398
398
by convention modify their arguments and thus are not pure. However, there are
399
- functions that have side effects and their name does not end with ` !` . For
400
- instance [` findfirst(regex, str)` ](@ref ) mutates its ` regex` argument or
401
- [` rand()` ](@ref ) changes ` Base.GLOBAL_RNG` :
402
-
403
- ``` julia-repl
404
- julia> using Base.Threads
405
-
406
- julia> nthreads()
407
- 4
408
-
409
- julia> function f()
410
- s = repeat(["123", "213", "231"], outer=1000)
411
- x = similar(s, Int)
412
- rx = r"1"
413
- @threads for i in 1:3000
414
- x[i] = findfirst(rx, s[i]).start
415
- end
416
- count(v -> v == 1, x)
417
- end
418
- f (generic function with 1 method)
419
-
420
- julia> f() # the correct result is 1000
421
- 1017
422
-
423
- julia> function g()
424
- a = zeros(1000)
425
- @threads for i in 1:1000
426
- a[i] = rand()
427
- end
428
- length(unique(a))
429
- end
430
- g (generic function with 1 method)
431
-
432
- julia> Random.seed!(1); g() # the result for a single thread is 1000
433
- 781
434
- ```
435
-
436
- In such cases one should redesign the code to avoid the possibility of a race condition or use
437
- [synchronization primitives](https: // docs. julialang. org/ en/ latest/ base/ multi- threading/ # Synchronization-Primitives-1).
438
-
439
- For example in order to fix ` findfirst` example above one needs to have a
440
- separate copy of ` rx` variable for each thread:
441
-
442
- ``` julia-repl
443
- julia> function f_fix()
444
- s = repeat(["123", "213", "231"], outer=1000)
445
- x = similar(s, Int)
446
- rx = [Regex("1") for i in 1:nthreads()]
447
- @threads for i in 1:3000
448
- x[i] = findfirst(rx[threadid()], s[i]).start
449
- end
450
- count(v -> v == 1, x)
451
- end
452
- f_fix (generic function with 1 method)
453
-
454
- julia> f_fix()
455
- 1000
456
- ```
457
-
458
- We now use ` Regex("1")` instead of ` r"1"` to make sure that Julia
459
- creates separate instances of ` Regex` object for each entry of ` rx` vector.
460
-
461
- The case of ` rand` is a bit more complex as we have to ensure that each thread
462
- uses non- overlapping pseudorandom number sequences. This can be simply ensured
463
- by using ` Future.randjump` function :
464
-
465
-
466
- ``` julia-repl
467
- julia> using Random; import Future
468
-
469
- julia> function g_fix(r)
470
- a = zeros(1000)
471
- @threads for i in 1:1000
472
- a[i] = rand(r[threadid()])
473
- end
474
- length(unique(a))
475
- end
476
- g_fix (generic function with 1 method)
477
-
478
- julia> r = let m = MersenneTwister(1)
479
- [m; accumulate(Future.randjump, fill(big(10)^20, nthreads()-1), init=m)]
480
- end;
481
-
482
- julia> g_fix(r)
483
- 1000
484
- ```
485
-
486
- We pass the ` r` vector to ` g_fix` as generating several RGNs is an expensive
487
- operation so we do not want to repeat it every time we run the function .
399
+ functions that have side effects and their name does not end with ` !` .
488
400
489
401
# # @threadcall (Experimental)
490
402
0 commit comments