Skip to content

Commit 587479a

Browse files
committed
fixup: switch default lock and event impl to be thread-safe
1 parent 2f8a809 commit 587479a

File tree

3 files changed

+40
-26
lines changed

3 files changed

+40
-26
lines changed

base/event.jl

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
AbstractLock
77
88
Abstract supertype describing types that
9-
implement the thread-safe synchronization primitives:
9+
implement the synchronization primitives:
1010
[`lock`](@ref), [`trylock`](@ref), [`unlock`](@ref), and [`islocked`](@ref).
1111
"""
1212
abstract type AbstractLock end
@@ -24,30 +24,33 @@ assert_havelock(l::AbstractLock, tid::Task) =
2424
assert_havelock(l::AbstractLock, tid::Nothing) = error("concurrency violation detected")
2525

2626
"""
27-
NotALock
27+
AlwaysLockedST
2828
29-
A struct that pretends to be always locked on the original thread it was allocated on,
29+
This struct does not implement a real lock, but instead
30+
pretends to be always locked on the original thread it was allocated on,
3031
and simply ignores all other interactions.
32+
It also does not synchronize tasks; for that use a [`CooperativeLock`](@ref) or a
33+
real lock such as [`RecursiveLock`](@ref).
3134
This can be used in the place of a real lock to, instead, simply and cheaply assert
3235
that the operation is only occurring on a single thread.
33-
And is thus functionally equivalent to allocating a real, recursive lock,
36+
And is thus functionally equivalent to allocating a real, recursive, task-unaware lock
3437
immediately calling `lock` on it, and then never calling a matching `unlock`,
3538
except that calling `lock` from another thread will throw a concurrency violation exception.
3639
"""
37-
struct NotALock <: AbstractLock
40+
struct AlwaysLockedST <: AbstractLock
3841
ownertid::Int16
39-
NotALock() = new(Threads.threadid())
42+
AlwaysLockedST() = new(Threads.threadid())
4043
end
41-
assert_havelock(l::NotALock) = assert_havelock(l, l.ownertid)
42-
lock(l::NotALock) = assert_havelock(l)
43-
unlock(l::NotALock) = assert_havelock(l)
44-
trylock(l::NotALock) = l.ownertid == Threads.threadid()
45-
islocked(::NotALock) = true
44+
assert_havelock(l::AlwaysLockedST) = assert_havelock(l, l.ownertid)
45+
lock(l::AlwaysLockedST) = assert_havelock(l)
46+
unlock(l::AlwaysLockedST) = assert_havelock(l)
47+
trylock(l::AlwaysLockedST) = l.ownertid == Threads.threadid()
48+
islocked(::AlwaysLockedST) = true
4649

4750
"""
4851
CooperativeLock
4952
50-
An optimistic lock, which can be used cheaply to check for missing
53+
An optimistic lock for cooperative tasks, which can be used cheaply to check for missing
5154
lock/unlock guards around `wait`, in the trivial (conflict-free, yield-free, single-threaded, non-recursive) case,
5255
without paying the cost for a full RecursiveLock.
5356
"""
@@ -56,9 +59,24 @@ mutable struct CooperativeLock <: AbstractLock
5659
CooperativeLock() = new(nothing)
5760
end
5861
assert_havelock(l::CooperativeLock) = assert_havelock(l, l.owner)
59-
lock(l::CooperativeLock) = (l.owner === nothing || error("concurrency violation detected"); l.owner = current_task(); nothing)
60-
unlock(l::CooperativeLock) = (assert_havelock(l); l.owner = nothing; nothing)
61-
trylock(l::CooperativeLock) = (l.owner === nothing ? (l.owner = current_task(); true) : false)
62+
function lock(l::CooperativeLock)
63+
l.owner === nothing || error("concurrency violation detected")
64+
l.owner = current_task()
65+
nothing
66+
end
67+
function unlock(l::CooperativeLock)
68+
assert_havelock(l)
69+
l.owner = nothing
70+
nothing
71+
end
72+
function trylock(l::CooperativeLock)
73+
if l.owner === nothing
74+
l.owner = current_task()
75+
return true
76+
else
77+
return false
78+
end
79+
end
6280
islocked(l::CooperativeLock) = l.owner !== nothing
6381

6482

@@ -142,15 +160,15 @@ function notify(c::GenericCondition, @nospecialize(arg), all, error)
142160
if all
143161
cnt = length(c.waitq)
144162
for t in c.waitq
145-
error ? schedule(t, arg, error=error) : schedule(t, arg)
163+
schedule(t, arg, error=error)
146164
end
147165
empty!(c.waitq)
148166
elseif !isempty(c.waitq)
149167
cnt = 1
150168
t = popfirst!(c.waitq)
151-
error ? schedule(t, arg, error=error) : schedule(t, arg)
169+
schedule(t, arg, error=error)
152170
end
153-
cnt
171+
return cnt
154172
end
155173

156174
notify_error(c::GenericCondition, err) = notify(c, err, true, true)
@@ -173,8 +191,6 @@ Create a level-triggered event source. Tasks that call [`wait`](@ref) on an
173191
After `notify` is called, the `Event` remains in a signaled state and
174192
tasks will no longer block when waiting for it.
175193
176-
This object is NOT thread-safe. See [`Threads.EventMT`](@ref) for a thread-safe version.
177-
178194
!!! compat "Julia 1.1"
179195
This functionality requires at least Julia 1.1.
180196
"""
@@ -215,8 +231,7 @@ const ConditionST = GenericCondition{CooperativeLock}
215231
const EventST = GenericEvent{CooperativeLock}
216232

217233
# default (Julia v1.0) is currently single-threaded
218-
const Condition = GenericCondition{NotALock}
219-
const Event = EventST
234+
const Condition = GenericCondition{AlwaysLockedST}
220235

221236

222237
## scheduler and work queue

base/lock.jl

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
Creates a re-entrant lock for synchronizing [`Task`](@ref)s.
88
The same task can acquire the lock as many times as required.
99
Each [`lock`](@ref) must be matched with an [`unlock`](@ref).
10-
11-
This lock is NOT thread-safe. See [`Threads.ReentrantLockMT`](@ref) for a thread-safe version.
1210
"""
1311
mutable struct GenericReentrantLock{ThreadLock<:AbstractLock} <: AbstractLock
1412
locked_by::Union{Task, Nothing}
@@ -20,7 +18,6 @@ end
2018

2119
# A basic single-threaded, Julia-aware lock:
2220
const ReentrantLockST = GenericReentrantLock{CooperativeLock}
23-
const ReentrantLock = ReentrantLockST # default (Julia v1.0) is currently single-threaded
2421

2522

2623
"""
@@ -122,7 +119,7 @@ function unlockall(rl::GenericReentrantLock)
122119
n == 0 && error("unlock count must match lock count")
123120
lock(rl.cond_wait)
124121
try
125-
rl.reentrancy_cnt == 0
122+
rl.reentrancy_cnt = 0
126123
rl.locked_by = nothing
127124
notify(rl.cond_wait)
128125
finally

base/sysimg.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,8 @@ include("task.jl")
317317
include("lock.jl")
318318
include("threads.jl")
319319
include("weakkeydict.jl")
320+
const ReentrantLock = Threads.ReentrantLockMT
321+
const Event = Threads.EventMT
320322

321323
# Logging
322324
include("logging.jl")

0 commit comments

Comments
 (0)