-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
253 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
using System.Threading; | ||
|
||
namespace BTDB.Locks; | ||
|
||
/// <summary> | ||
/// Super optimistic reader writer lock. | ||
/// It is suitable for rare writes and many parallel reads. | ||
/// Read cannot hang or crash or corrupt write when write is in progress. | ||
/// Readers never block writer. Writer blocks other writers. Writer lock is not reentrant. | ||
/// It is struct and its size is just 4 bytes. | ||
/// Pattern for read is this: | ||
/// var seqCounter = seqLock.StartRead(); | ||
/// retry: | ||
/// try | ||
/// { | ||
/// // read data | ||
/// if (seqLock.RetryRead(ref seqCounter)) goto retry; | ||
/// } | ||
/// catch | ||
/// { | ||
/// if (seqLock.RetryRead(ref seqCounter)) goto retry; | ||
/// throw; | ||
/// } | ||
/// | ||
/// Pattern for write is this: | ||
/// seqLock.StartWrite(); | ||
/// try | ||
/// { | ||
/// // write data | ||
/// } | ||
/// finally | ||
/// { | ||
/// seqLock.EndWrite(); | ||
/// } | ||
/// </summary> | ||
public struct SeqLock | ||
{ | ||
uint _counter; | ||
|
||
public uint StartRead() | ||
{ | ||
SpinWait spin = default; | ||
var res = _counter; | ||
Interlocked.MemoryBarrier(); | ||
while ((res & 1u) != 0) | ||
{ | ||
spin.SpinOnce(); | ||
res = _counter; | ||
Interlocked.MemoryBarrier(); | ||
} | ||
|
||
return res; | ||
} | ||
|
||
public bool RetryRead(ref uint seqCounter) | ||
{ | ||
Interlocked.MemoryBarrier(); | ||
var current = _counter; | ||
if (seqCounter != current) | ||
{ | ||
SpinWait spin = default; | ||
while ((current & 1u) != 0) | ||
{ | ||
spin.SpinOnce(); | ||
current = _counter; | ||
} | ||
|
||
Interlocked.MemoryBarrier(); | ||
seqCounter = current; | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
public void StartWrite() | ||
{ | ||
SpinWait spin = default; | ||
while (true) | ||
{ | ||
var counter = _counter; | ||
while ((counter & 1u) != 0) | ||
{ | ||
spin.SpinOnce(); | ||
counter = _counter; | ||
} | ||
|
||
if (Interlocked.CompareExchange(ref _counter, counter + 1u, counter) == counter) | ||
break; | ||
spin.SpinOnce(); | ||
} | ||
} | ||
|
||
public void EndWrite() | ||
{ | ||
Interlocked.MemoryBarrier(); | ||
_counter++; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
using System.Threading.Tasks; | ||
using BTDB.Locks; | ||
using Xunit; | ||
|
||
namespace BTDBTest; | ||
|
||
public class SeqLockTests | ||
{ | ||
[Fact] | ||
public void SimpleWriteLockWorks() | ||
{ | ||
var data = 0; | ||
var seqLock = new SeqLock(); | ||
seqLock.StartWrite(); | ||
data++; | ||
seqLock.EndWrite(); | ||
|
||
Assert.Equal(1, data); | ||
} | ||
|
||
[Fact] | ||
public void SimpleReadLockWorks() | ||
{ | ||
var data = 0; | ||
var seqLock = new SeqLock(); | ||
seqLock.StartWrite(); | ||
data++; | ||
seqLock.EndWrite(); | ||
|
||
var seqCounter = seqLock.StartRead(); | ||
Assert.Equal(1, data); | ||
Assert.False(seqLock.RetryRead(ref seqCounter)); | ||
} | ||
|
||
[Fact] | ||
public void ComplexReadLockWorks() | ||
{ | ||
var data = 0; | ||
var seqLock = new SeqLock(); | ||
|
||
Task.Run(() => | ||
{ | ||
seqLock.StartWrite(); | ||
data++; | ||
seqLock.EndWrite(); | ||
}); | ||
|
||
int readData; | ||
do | ||
{ | ||
var seqCounter = seqLock.StartRead(); | ||
retry: | ||
readData = data; | ||
if (seqLock.RetryRead(ref seqCounter)) goto retry; | ||
} while (readData != 1); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
using System; | ||
using System.Threading; | ||
using BenchmarkDotNet.Attributes; | ||
using BTDB.KVDBLayer; | ||
using BTDB.Locks; | ||
|
||
namespace SimpleTester; | ||
|
||
// | Method | Mean | Error | StdDev | | ||
// |--------------------------- |----------:|----------:|----------:| | ||
// | SeqLock_Read | 1.239 ns | 0.0044 ns | 0.0039 ns | | ||
// | ReaderWriterLockSlim_Read | 12.508 ns | 0.0304 ns | 0.0284 ns | | ||
// | MonitorLock_Read | 8.579 ns | 0.0291 ns | 0.0272 ns | | ||
// | SeqLock_Write | 4.148 ns | 0.0252 ns | 0.0236 ns | | ||
// | ReaderWriterLockSlim_Write | 13.532 ns | 0.0323 ns | 0.0302 ns | | ||
// | MonitorLock_Write | 8.145 ns | 0.0272 ns | 0.0254 ns | | ||
|
||
[InProcess] | ||
public class BenchLockTest | ||
{ | ||
ulong _global; | ||
int _lockCounter; | ||
ReaderWriterLockSlim _lock = new(); | ||
SeqLock _seqLock; | ||
object _lockObj = new(); | ||
|
||
[GlobalSetup] | ||
public void GlobalSetup() | ||
{ | ||
Interlocked.Increment(ref _lockCounter); | ||
_global = (ulong)Random.Shared.Next(); | ||
Interlocked.Increment(ref _lockCounter); | ||
} | ||
|
||
[Benchmark] | ||
public ulong SeqLock_Read() | ||
{ | ||
var seqCounter = _seqLock.StartRead(); | ||
retry: | ||
var res = _global; | ||
if (_seqLock.RetryRead(ref seqCounter)) goto retry; | ||
return res; | ||
} | ||
|
||
[Benchmark] | ||
public ulong ReaderWriterLockSlim_Read() | ||
{ | ||
using (_lock.ReadLock()) | ||
{ | ||
return _global; | ||
} | ||
} | ||
|
||
[Benchmark] | ||
public ulong MonitorLock_Read() | ||
{ | ||
lock (_lockObj) | ||
{ | ||
return _global; | ||
} | ||
} | ||
|
||
[Benchmark] | ||
public void SeqLock_Write() | ||
{ | ||
_seqLock.StartWrite(); | ||
try | ||
{ | ||
_global++; | ||
} | ||
finally | ||
{ | ||
_seqLock.EndWrite(); | ||
} | ||
} | ||
|
||
[Benchmark] | ||
public void ReaderWriterLockSlim_Write() | ||
{ | ||
using (_lock.WriteLock()) | ||
{ | ||
_global++; | ||
} | ||
} | ||
|
||
[Benchmark] | ||
public void MonitorLock_Write() | ||
{ | ||
lock (_lockObj) | ||
{ | ||
_global++; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters