diff --git a/NEWS.md b/NEWS.md index 9280a30ea42ac..c9c4dbbd534c7 100644 --- a/NEWS.md +++ b/NEWS.md @@ -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 -------------------- diff --git a/base/exports.jl b/base/exports.jl index ff541ca5404c0..4cd7883b04f63 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -656,6 +656,7 @@ export istaskfailed, lock, notify, + Lockable, ReentrantLock, schedule, task_local_storage, diff --git a/base/lock.jl b/base/lock.jl index 646189ea196fc..3de0430e69fc5 100644 --- a/base/lock.jl +++ b/base/lock.jl @@ -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 + +# 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 diff --git a/test/misc.jl b/test/misc.jl index 00e44099f6d2f..45f3cfc64e8da 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -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)