keywords: Julia,Concurrency CJKmainfont: KaiTi –-

用Julia实现读写锁

本文受Implementing reader-writer locks一文启发,采用Julia实现读写锁(原文用的是Golang)。

为何需要读写锁

在多线程编程过程中,对于一些关键资源,需要对其加锁,以保证同一时刻只有一个线程在操作数据。不过在某些场景下,加锁带来的代价会比较大。如果只有一个互斥锁,那么当读取操作的次数远大于写入操作的次数时,由于每次读取都会对数据加锁,必然带来额外的开销。显然,如果多次读取操作之间没有写入操作,那么这段时间内其实时不需要对数据加锁的,于是乎,便有了读写锁专门用于提升此类场景下锁的效率。

接下来,我们将一步步实现高效的读写锁。

MutexAsRWLock

先假设只有一把锁,看看其效率如何:

struct MutexAsRWLock
    m::Threads.Mutex
    MutexAsRWLock() = new(Threads.Mutex())
end

read_lock(l::MutexAsRWLock) = lock(l.m)
read_unlock(l::MutexAsRWLock) = unlock(l.m)
write_lock(l::MutexAsRWLock) = lock(l.m)
write_unlock(l::MutexAsRWLock) = unlock(l.m)

然后用原文中提到的测试方法,测试下这种情况下,read_lockwrite_lock获取锁的时间:

julia> batch_test_rwlock(rwl, 1000, 10)
(2.0248251801001482e-8, 1.9721886599999977e-8)

ReaderCountRWLock

接下来将其换成一种最简单的实现:读写锁共用一个互斥锁,不过,获取写锁时,如果当前已经有了读锁(count大于1),那么就将其释放掉,然后循环下去:

mutable struct ReaderCountRWLock
    m::Threads.Mutex
    reader_count::Int
    ReaderCountRWLock() = new(Threads.Mutex(), 0)
end

function read_lock(l::ReaderCountRWLock)
    lock(l.m) do
    l.reader_count += 1
    end
end

function read_unlock(l::ReaderCountRWLock)
    lock(l.m) do
        l.reader_count -= 1
        if l.reader_count < 0
            error("reader count negative")
        end
    end
end

function write_lock(l::ReaderCountRWLock)
    while true
        lock(l.m)
        if l.reader_count > 0
            unlock(l.m)
        else
            break
        end
    end
end

function write_unlock(l::ReaderCountRWLock)
    unlock(l.m)
end
julia> batch_test_rwlock(rwl, 1000, 10)
(1.2749199800001581e-10, 4.536146970000002e-8)

可以看到,读锁的时间大大降低了,但是写入锁的时间稍稍增加了一些。