lock
や ReaderWriterLockSlim
による同期処理のメモ
lock
で同期処理
var stopWatch = new Stopwatch();
stopWatch.Reset();
stopWatch.Start();
Parallel.For(0, 10, i =>
{
lock (stopWatch)
{
for (int loop = 0; loop < 10; loop++)
{
Trace.Write(i);
Thread.Sleep(10);
}
Trace.Write(Environment.NewLine);
}
});
stopWatch.Stop();
Debug.WriteLine("Total time "
+ stopWatch.ElapsedMilliseconds); // 1000より大きくなる
※コレを実行すると出力される数値は同じ数値であればひとまとまりに出力される。
ReaderWriterLockSlim
なんかロックするのに便利なクラスらしい。
デッドロックしにくいとかなんとか。速度は lock
よりも遅い。
Enter****Lock
でロックを取得して、Exit****Lock
でロックを手放す。
書き込みロック
var lockSlim = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
var stopWatch = Stopwatch.StartNew();
Parallel.For(0, 10, i =>
{
lockSlim.EnterWriteLock();
for (int loop = 0; loop < 10; loop++)
{
Trace.Write(i);
Thread.Sleep(10);
}
Trace.Write(Environment.NewLine);
lockSlim.ExitWriteLock();
});
stopWatch.Stop();
Debug.WriteLine("Total time "
+ stopWatch.ElapsedMilliseconds); // 1000より大きくなる
これの結果は lock
の場合と同じ。
書き込みロックを取得した場合は、後続の処理はロックが外れるまで待つことになる。
読み込みロック
var lockSlim = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
var stopWatch = new Stopwatch();
stopWatch.Reset();
stopWatch.Start();
Parallel.For(0, 10, i =>
{
lockSlim.EnterReadLock();
for (int loop = 0; loop < 10; loop++)
{
Trace.Write(i);
Thread.Sleep(10);
}
Trace.Write(Environment.NewLine);
lockSlim.ExitReadLock();
});
stopWatch.Stop();
Debug.WriteLine("Total time "
+ stopWatch.ElapsedMilliseconds); // 100より大きくなる
これは出力の順番はバラバラとなる。 読み込みロックは、他の処理がロックを握ってても取得できるので。。
var lockSlim = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
try
{
lockSlim.EnterReadLock();
lockSlim.EnterWriteLock();
}
catch (LockRecursionException ex)
{
Trace.WriteLine(ex.Message);
}
finally
{
lockSlim.ExitReadLock();
}
例外が飛ぶ。
例外出力内容は :
読み取りロックを保持しながら、書き込みロックを取得することはできません。
このパターンでは、デッドロックの可能性が高まります。
書き込みロックを取得する前に、読み取りロックが解放されていることを確認してください。
アップグレードが必要な場合は、読み取りロックの代わりに、アップグレード可能なロックを使用します。
ReaderWriterLockSlim
と例外と
var lockSlim = new ReaderWriterLockSlim();
Parallel.For(0, 10, i =>
{
try
{
Trace.WriteLine("try " + i);
lockSlim.EnterWriteLock();
throw new Exception();
lockSlim.ExitWriteLock();
}
catch
{
Trace.WriteLine("catch " + i);
}
});
途中で例外が投げられると、ロックが解放されず、ロック解放待ちのスレッドが終わらない。
using (var lockSlim = new ReaderWriterLockSlim())
{
Parallel.For(0, 10, i =>
{
try
{
Trace.WriteLine("try " + i);
lockSlim.EnterWriteLock();
throw new Exception();
}
catch
{
Trace.WriteLine("catch " + i);
}
finally
{
if (lockSlim.IsWriteLockHeld)
{
lockSlim.ExitWriteLock();
}
}
}); // parallel for
} // using
finally
できちんとロックを放す。また、ロックを持ってないのにExitすると System.Threading.SynchronizationLockException
が投げられる。
また ReaderWriteLockSlim
は IDisposable
を採用しているのでDisposeすること。
また、ロックを保持したまま ReaderWriteLockSlim
インスタンスのファイナライズが実行される場合も SynchronizationLockException
が投げられる。
組み合わせて実行してみる
var lockSlim = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
var watch = new Stopwatch();
watch.Start();
Parallel.For(0, 10, i =>
{
if (i % 2 == 0)
{
lockSlim.EnterReadLock();
Trace.WriteLine("enter read lock - " + watch.Elapsed.Seconds);
Thread.Sleep(1000);
lockSlim.ExitReadLock();
Trace.WriteLine("exit read lock - " + watch.Elapsed.Seconds);
}
else
{
lockSlim.EnterWriteLock();
Trace.WriteLine("enter write lock - " + watch.Elapsed.Seconds);
Thread.Sleep(1000);
lockSlim.ExitWriteLock();
Trace.WriteLine("exit write lock- " + watch.Elapsed.Seconds);
}
});
"enter read lock"
の出力は実行してすぐに全部出力されるけど、
"enter write lock"
の出力は順次出力される。
結果
enter read lock - 0
enter read lock - 0
enter read lock - 0
enter read lock - 0
enter read lock - 0
exit read lock - 1
exit read lock - 1
exit read lock - 1
exit read lock - 1
enter write lock - 1
exit read lock - 1
exit write lock- 2
enter write lock - 2
exit write lock- 3
enter write lock - 3
exit write lock- 4
enter write lock - 4
exit write lock- 5
enter write lock - 5
exit write lock- 6
再帰的なロックの取得
using (var l = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion))
{
//
// Read Lock 取得状態でロックを取得すると
//
l.EnterReadLock();
TraceThrows(() => l.EnterReadLock()); // このモードでは、再帰的な読み取りロックは取得できません。
TraceThrows(() => l.EnterWriteLock()); // 読み取りロックを保持しながら、書き込みロックを取得することはできません。このパターンでは、デッドロックの可能性が高まります。書き込みロックを取得する前に、読み取りロックが解放されていることを確認してください。アップグレードが必要な場合は、読み取りロックの代わりに、アップグレード可能なロックを使用します。
l.ExitReadLock();
//
// Write Lock 取得状態でロックを取得すると
//
l.EnterWriteLock();
TraceThrows(() => l.EnterReadLock()); // このモードでは、書き込みロックを保持しながら読み取りロックを取得することはできません。
TraceThrows(() => l.EnterWriteLock()); // このモードでは、再帰的な書き込みロックは取得できません。
l.ExitWriteLock();
} // using
using (var l = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion))
{
//
// Read Lock 取得状態でロックを取得すると
//
l.EnterReadLock();
TraceThrows(() => l.EnterReadLock()); // No exception.
TraceThrows(() => l.EnterWriteLock()); // 読み取りロックを保持しながら、書き込みロックを取得することはできません。このパターンでは、デッドロックの可能性が高まります。書き込みロックを取得する前に、読み取りロックが解放されていることを確認してください。アップグレードが必要な場合は、読み取りロックの代わりに、アップグレード可能なロックを使用します。
l.ExitReadLock();
l.ExitReadLock();
//
// Write Lock 取得状態でロックを取得すると
//
l.EnterWriteLock();
TraceThrows(() => l.EnterReadLock()); // No exception.
TraceThrows(() => l.EnterWriteLock()); // No exception.
l.ExitWriteLock();
l.ExitWriteLock();
l.ExitReadLock();
} // using
※ReaderWriterLockSlim
の引数なしのコンストラクタは NoRecursion
を渡した時と同じ。
アップグレード可能なロックはいまいち分からなかったのでまたいつか。。