Skip to content

Commit c6ccf20

Browse files
KronosTheLateinkydragonnsajkooscardssmith
authored
Mention Base.Lockable in "multi-threading.md" (#58107)
When I originally learned about multithreading and locks (and ended up rewriting [https://docs.julialang.org/en/v1/manual/multi-threading/#man-using-locks](Using locks to avoid data-races)), I was puzzled to find that the association of a lock and a value was the mental task of the programmer, and not the programmatic task of the program. I made that explicit in that section of the manual, writing the following: > Note that the link between a lock and a variable is made by the programmer, and not the program. I was therefore very happy to see Base.Lockable introduced in Julia 1.11. What was missing, was any mention of it in the relevant section of the manual. This PR adds a subsection under locks (So a forth level of headings, not sure if that is fine), showcasing and reccomending the use of Base.Lockable. I have made heavy use of comments in the example, which breaks with the general style, but adds valuable interpretation along the way. I am very open restructuring that part in particular. --------- Co-authored-by: Chengyu Han <[email protected]> Co-authored-by: Neven Sajko <[email protected]> Co-authored-by: Oscar Smith <[email protected]>
1 parent 8773db7 commit c6ccf20

File tree

1 file changed

+45
-1
lines changed

1 file changed

+45
-1
lines changed

doc/src/manual/multi-threading.md

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,9 @@ bad_read2(a) # it is NOT safe to access `a` here
306306
```
307307

308308
### [Using locks to avoid data-races](@id man-using-locks)
309-
An important tool to avoid data-races, and thereby write thread-safe code, is the concept of a "lock". A lock can be locked and unlocked. If a thread has locked a lock, and not unlocked it, it is said to "hold" the lock. If there is only one lock, and we write code the requires holding the lock to access some data, we can ensure that multiple threads will never access the same data simultaneously. Note that the link between a lock and a variable is made by the programmer, and not the program.
309+
An important tool for avoiding data races, and writing thread-safe code in general, is the concept of a "lock". A lock can be locked and unlocked. If a thread has locked a lock, and not unlocked it, it is said to "hold" the lock. If there is only one lock, and we write code that requires holding the lock to access some data, we can ensure that multiple threads will never access the same data simultaneously.
310+
311+
Note that the link between a lock and a variable is made by the programmer, and not the program. A helper-type [`Base.Lockable`](@ref) exists that helps you associate a lock and a value. This is often more safe than keeping track yourself, and is detailed under [Using Base.Lockable to associate a lock and a value](@ref man-lockable).
310312

311313
For example, we can create a lock `my_lock`, and lock it while we mutate a variable `my_variable`. This is done most simply with the `@lock` macro:
312314

@@ -342,6 +344,48 @@ julia> begin
342344
All three options are equivalent. Note how the final version requires an explicit `try`-block to ensure that the lock is always unlocked, whereas the first two version do this internally. One should always use the lock pattern above when changing data (such as assigning
343345
to a global or closure variable) accessed by other threads. Failing to do this could have unforeseen and serious consequences.
344346

347+
#### [Using Base.Lockable to associate a lock and a value](@id man-lockable)
348+
As mentioned in the previous section, the helper-type [`Base.Lockable`](@ref) can be used to programmatically ensure the association between a lock and a value. This is generally recommended, as it is both less prone to error and more readable for others compared to having the association only by convention.
349+
350+
Any object can be wrapped in `Base.Lockable`:
351+
```julia-repl
352+
julia> my_array = [];
353+
354+
julia> my_locked_array = Base.Lockable(my_array);
355+
```
356+
357+
If the lock is held, the underlying object can be accessed with the empty indexing notation:
358+
```julia-repl
359+
julia> begin
360+
lock(my_locked_array)
361+
try
362+
push!(my_locked_array[], 1)
363+
finally
364+
unlock(my_locked_array)
365+
end
366+
end
367+
1-element Vector{Any}:
368+
1
369+
```
370+
371+
It is usually easier and safer to pass a function as the first argument to `lock`. The function is applied to the unlocked object, and the locking/unlocking is handled automatically:
372+
```julia-repl
373+
julia> lock(x -> push!(x, 2), my_locked_array);
374+
375+
julia> lock(display, my_locked_array)
376+
2-element Vector{Any}:
377+
1
378+
2
379+
380+
julia> lock(my_locked_array) do x
381+
x[1] = π
382+
display(x)
383+
end
384+
2-element Vector{Any}:
385+
π = 3.1415926535897...
386+
2
387+
```
388+
345389
### [Atomic Operations](@id man-atomic-operations)
346390

347391
Julia supports accessing and modifying values *atomically*, that is, in a thread-safe way to avoid

0 commit comments

Comments
 (0)