Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: Add Lockable{T, L<:AbstractLock} struct #34400

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Language changes

Multi-threading changes
-----------------------

* There is a new struct `Lockable{T, L<:AbstractLock}` that makes it easy to bundle a resource and its lock together.

Build system changes
--------------------
Expand Down
1 change: 1 addition & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,7 @@ export
istaskfailed,
lock,
notify,
Lockable,
ReentrantLock,
schedule,
task_local_storage,
Expand Down
52 changes: 52 additions & 0 deletions base/lock.jl
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,58 @@ function lock(f, l::AbstractLock)
end
end

"""
Lockable(value, lock = ReentrantLock())

Creates a `Lockable` object that wraps `value` and
associates it with the provided `lock`.

!!! compat "Julia 1.5"
Requires at least Julia 1.5.
"""
struct Lockable{T, L<:AbstractLock}
value::T
lock::L
end

"""

Lockable(value)

Creates a `Lockable` object that wraps `value` and
associates it with a newly created `ReentrantLock`.

!!! compat "Julia 1.5"
Requires at least Julia 1.5.
"""
Lockable(value) = Lockable(value, ReentrantLock())

"""
lock(f::Function, l::Lockable)

Acquire the lock associated with `l`, execute `f` with the lock held,
and release the lock when `f` returns. `f` will receive one positional
argument: the value wrapped by `l`. If the lock is already locked by a
different task/thread, wait for it to become available.

When this function returns, the `lock` has been released, so the caller should
not attempt to `unlock` it.

!!! compat "Julia 1.5"
Requires at least Julia 1.5.
"""
function lock(f, l::Lockable)
lock(l.lock) do
f(l.value)
end
end
DilumAluthge marked this conversation as resolved.
Show resolved Hide resolved

# implement the rest of the Lock interface on Lockable
islocked(l::Lockable) = islocked(l.lock)
lock(l::Lockable) = lock(l.lock)
trylock(l::Lockable) = trylock(l.lock)
unlock(l::Lockable) = unlock(l.lock)

function trylock(f, l::AbstractLock)
if trylock(l)
try
Expand Down
30 changes: 30 additions & 0 deletions test/misc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,36 @@ let l = ReentrantLock()
@test_throws ErrorException unlock(l)
end

# Lockable{T, L<:AbstractLock}
let # test the constructor `Lockable(value, lock)`
lockable = Lockable(Dict("foo" => "hello"), ReentrantLock())
@test lockable.value["foo"] == "hello"
lock(lockable) do d
@test d["foo"] == "hello"
end
lock(lockable) do d
d["foo"] = "goodbye"
end
@test lockable.value["foo"] == "goodbye"
lock(lockable) do d
@test d["foo"] == "goodbye"
end
end
let # test the constructor `Lockable(value)`
lockable = Lockable(Dict("foo" => "hello"))
@test lockable.value["foo"] == "hello"
lock(lockable) do d
@test d["foo"] == "hello"
end
lock(lockable) do d
d["foo"] = "goodbye"
end
@test lockable.value["foo"] == "goodbye"
lock(lockable) do d
@test d["foo"] == "goodbye"
end
end

# task switching

@noinline function f6597(c)
Expand Down