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

Update to use Threads.Condition and be write-preferred #15

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
35 changes: 3 additions & 32 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
@@ -1,40 +1,27 @@
name: CI
# Run on master, tags, or any pull request
on:
schedule:
- cron: '0 2 * * *' # Daily at 2 AM UTC (8 PM CST)
push:
branches: [master]
tags: ["*"]
pull_request:
jobs:
test:
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }}
env:
JULIA_NUM_THREADS: 2
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
version:
- "1.0" # LTS
- "1.6" # LTS
- "1" # Latest Release
os:
- ubuntu-latest
- macOS-latest
- windows-latest
arch:
- x64
- x86
exclude:
# Test 32-bit only on Linux
- os: macOS-latest
arch: x86
- os: windows-latest
arch: x86
include:
# Add a 1.5 job because that's what Invenia actually uses
- os: ubuntu-latest
version: 1.5
arch: x64
steps:
- uses: actions/checkout@v2
- uses: julia-actions/setup-julia@v1
Expand All @@ -58,19 +45,3 @@ jobs:
- uses: codecov/codecov-action@v1
with:
file: lcov.info

slack:
name: Notify Slack Failure
needs: test
runs-on: ubuntu-latest
if: always() && github.event_name == 'schedule'
steps:
- uses: technote-space/workflow-conclusion-action@v2
- uses: voxmedia/github-action-slack-notify-build@v1
if: env.WORKFLOW_CONCLUSION == 'failure'
with:
channel: nightly-dev
status: FAILED
color: danger
env:
SLACK_BOT_TOKEN: ${{ secrets.DEV_SLACK_BOT_TOKEN }}
6 changes: 3 additions & 3 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
name = "ReadWriteLocks"
uuid = "c9a1755d-84d0-58e0-8f48-2f3129b1cb7a"
authors = "Invenia Technical Computing"
version = "0.1.0"
authors = "Eric Davies, Nick Robinson"
version = "1.0.0"

[compat]
julia = "1"
julia = "1.6"

[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Expand Down
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,17 @@
using ReadWriteLocks

# The type provided by this package is ReadWriteLock.
# It has a single constructor.
rwlock = ReadWriteLock()

# This lock provides access to a read lock and a write lock
rlock = read_lock(rwlock)
wlock = write_lock(rwlock)
rlock = get_read_lock(rwlock)
wlock = get_write_lock(rwlock)

# To acquire a read lock:
lock!(rlock)
lock(rlock)

# To release a read lock:
unlock!(rlock)
unlock(rlock)

#=
Write locks provide the same interface.
Expand All @@ -41,5 +40,5 @@ ReadWriteLocks.jl is provided under the MIT "Expat" License.

## Citation

This is a reimplementation of the original Java source from:
This is largely based upon the original Java source from:
> M. Herlihy and N. Shavit, “8.3.1 Simple Readers-Writers Lock,” in The art of multiprocessor programming, revised first edition, Rev. 1st., Waltham, Massachusetts: Morgan Kaufmann, 2012, pp. 184–185.
83 changes: 41 additions & 42 deletions src/ReadWriteLocks.jl
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
module ReadWriteLocks

using Base: lock, unlock
if VERSION < v"1.2.0-DEV.28"
using Base.Threads: AbstractLock
else
using Base: AbstractLock
end
using Base: AbstractLock, lock, unlock

export ReadWriteLock, read_lock, write_lock, lock!, unlock!
export ReadWriteLock, get_read_lock, get_write_lock
export readlock, readunlock

struct ReadLock{T<:AbstractLock}
rwlock::T
Expand All @@ -17,95 +13,98 @@ struct WriteLock{T<:AbstractLock}
rwlock::T
end

# Need Julia VERSION > v"1.2.0-DEV.28` to have `ReentrantLock <: AbstractLock`
LockTypes = Union{AbstractLock, ReentrantLock}
mutable struct ReadWriteLock{L<:LockTypes} <: AbstractLock
mutable struct ReadWriteLock <: AbstractLock
readers::Int
writer::Bool
lock::L # reentrant mutex
condition::Condition
condition::Threads.Condition
read_lock::ReadLock
write_lock::WriteLock

function ReadWriteLock(
readers::Int=0,
writer::Bool=false,
lock::L=ReentrantLock(),
condition::Condition=Condition(),
) where L <: LockTypes
rwlock = new{L}(readers, writer, lock, condition)
condition::Threads.Condition=Threads.Condition(),
)
rwlock = new(readers, writer, condition)
rwlock.read_lock = ReadLock(rwlock)
rwlock.write_lock = WriteLock(rwlock)

return rwlock
end
end

read_lock(rwlock::ReadWriteLock) = rwlock.read_lock
write_lock(rwlock::ReadWriteLock) = rwlock.write_lock
get_read_lock(rwlock::ReadWriteLock) = rwlock.read_lock
get_write_lock(rwlock::ReadWriteLock) = rwlock.write_lock

function lock!(read_lock::ReadLock)
function Base.lock(read_lock::ReadLock)
rwlock = read_lock.rwlock
lock(rwlock.lock)

lock(rwlock.condition)
try
while rwlock.writer
wait(rwlock.condition)
end

rwlock.readers += 1
finally
unlock(rwlock.lock)
# @debug "readlock done" rwlock.readers rwlock.writer
unlock(rwlock.condition)
end

return nothing
end

function unlock!(read_lock::ReadLock)
function Base.unlock(read_lock::ReadLock)
rwlock = read_lock.rwlock
lock(rwlock.lock)

lock(rwlock.condition)
try
rwlock.readers -= 1
if rwlock.readers == 0
notify(rwlock.condition; all=true)
end
finally
unlock(rwlock.lock)
# @debug "readunlock done" rwlock.readers rwlock.writer
unlock(rwlock.condition)
end

return nothing
end

function lock!(write_lock::WriteLock)
function Base.lock(write_lock::WriteLock)
rwlock = write_lock.rwlock
lock(rwlock.lock)

lock(rwlock.condition)
try
while rwlock.readers > 0 || rwlock.writer
while rwlock.writer
wait(rwlock.condition)
end

rwlock.writer = true
while rwlock.readers > 0
wait(rwlock.condition)
end
finally
unlock(rwlock.lock)
unlock(rwlock.condition)
# @debug "lock done" rwlock.readers rwlock.writer
end

return nothing
end

function unlock!(write_lock::WriteLock)
function Base.unlock(write_lock::WriteLock)
rwlock = write_lock.rwlock
lock(rwlock.lock)

lock(rwlock.condition)
try
rwlock.writer = false
notify(rwlock.condition; all=true)
finally
unlock(rwlock.lock)
# @debug "unlock done" rwlock.readers rwlock.writer
unlock(rwlock.condition)
end

return nothing
end

Base.lock(rwlock::ReadWriteLock) = lock(get_write_lock(rwlock))
Base.unlock(rwlock::ReadWriteLock) = unlock(get_write_lock(rwlock))
# Reading doesn't count as locked.
Base.islocked(rwlock::ReadWriteLock) = iswriting(rwlock)
# To be writing we must have no readers, else `writer` just indicates a writer is waiting.
iswriting(rwlock::ReadWriteLock) = Base.@lock rwlock.condition ((rwlock.readers == 0) && rwlock.writer)
isreading(rwlock::ReadWriteLock) = Base.@lock rwlock.condition (rwlock.readers > 0)

readlock(rwlock::ReadWriteLock) = lock(get_read_lock(rwlock))
readunlock(rwlock::ReadWriteLock) = unlock(get_read_lock(rwlock))

end # module
Loading