Ich habe mit Sammlungen und Threading gespielt und bin auf die raffinierten Erweiterungsmethoden gestoßen, die Leute erstellt haben, um die Verwendung von ReaderWriterLockSlim zu erleichtern, indem das IDisposable-Muster erlaubt wird.ReaderWriterLockSlim Erweiterungsmethode Leistung
Ich glaube jedoch, dass ich zu der Erkenntnis gekommen bin, dass etwas in der Implementierung ein Performance-Killer ist. Mir ist klar, dass Erweiterungsmethoden die Leistung nicht wirklich beeinflussen sollen. Daher nehme ich an, dass etwas in der Implementierung die Ursache ist ... die Menge der geschaffenen/gesammelten Einwegstrukturen?
Hier einige Testcode:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Diagnostics;
namespace LockPlay {
static class RWLSExtension {
struct Disposable : IDisposable {
readonly Action _action;
public Disposable(Action action) {
_action = action;
}
public void Dispose() {
_action();
}
} // end struct
public static IDisposable ReadLock(this ReaderWriterLockSlim rwls) {
rwls.EnterReadLock();
return new Disposable(rwls.ExitReadLock);
}
public static IDisposable UpgradableReadLock(this ReaderWriterLockSlim rwls) {
rwls.EnterUpgradeableReadLock();
return new Disposable(rwls.ExitUpgradeableReadLock);
}
public static IDisposable WriteLock(this ReaderWriterLockSlim rwls) {
rwls.EnterWriteLock();
return new Disposable(rwls.ExitWriteLock);
}
} // end class
class Program {
class MonitorList<T> : List<T>, IList<T> {
object _syncLock = new object();
public MonitorList(IEnumerable<T> collection) : base(collection) { }
T IList<T>.this[int index] {
get {
lock(_syncLock)
return base[index];
}
set {
lock(_syncLock)
base[index] = value;
}
}
} // end class
class RWLSList<T> : List<T>, IList<T> {
ReaderWriterLockSlim _rwls = new ReaderWriterLockSlim();
public RWLSList(IEnumerable<T> collection) : base(collection) { }
T IList<T>.this[int index] {
get {
try {
_rwls.EnterReadLock();
return base[index];
} finally {
_rwls.ExitReadLock();
}
}
set {
try {
_rwls.EnterWriteLock();
base[index] = value;
} finally {
_rwls.ExitWriteLock();
}
}
}
} // end class
class RWLSExtList<T> : List<T>, IList<T> {
ReaderWriterLockSlim _rwls = new ReaderWriterLockSlim();
public RWLSExtList(IEnumerable<T> collection) : base(collection) { }
T IList<T>.this[int index] {
get {
using(_rwls.ReadLock())
return base[index];
}
set {
using(_rwls.WriteLock())
base[index] = value;
}
}
} // end class
static void Main(string[] args) {
const int ITERATIONS = 100;
const int WORK = 10000;
const int WRITE_THREADS = 4;
const int READ_THREADS = WRITE_THREADS * 3;
// create data - first List is for comparison only... not thread safe
int[] copy = new int[WORK];
IList<int>[] l = { new List<int>(copy), new MonitorList<int>(copy), new RWLSList<int>(copy), new RWLSExtList<int>(copy) };
// test each list
Thread[] writeThreads = new Thread[WRITE_THREADS];
Thread[] readThreads = new Thread[READ_THREADS];
foreach(var list in l) {
Stopwatch sw = Stopwatch.StartNew();
for(int k=0; k < ITERATIONS; k++) {
for(int i = 0; i < writeThreads.Length; i++) {
writeThreads[i] = new Thread(p => {
IList<int> il = p as IList<int>;
int c = il.Count;
for(int j = 0; j < c; j++) {
il[j] = j;
}
});
writeThreads[i].Start(list);
}
for(int i = 0; i < readThreads.Length; i++) {
readThreads[i] = new Thread(p => {
IList<int> il = p as IList<int>;
int c = il.Count;
for(int j = 0; j < c; j++) {
int temp = il[j];
}
});
readThreads[i].Start(list);
}
for(int i = 0; i < readThreads.Length; i++)
readThreads[i].Join();
for(int i = 0; i < writeThreads.Length; i++)
writeThreads[i].Join();
};
sw.Stop();
Console.WriteLine("time: {0} class: {1}", sw.Elapsed, list.GetType());
}
Console.WriteLine("DONE");
Console.ReadLine();
}
} // end class
} // end namespace
Hier ist ein typisches Ergebnis:
time: 00:00:03.0965242 class: System.Collections.Generic.List`1[System.Int32] time: 00:00:11.9194573 class: LockPlay.Program+MonitorList`1[System.Int32] time: 00:00:08.9510258 class: LockPlay.Program+RWLSList`1[System.Int32] time: 00:00:16.9888435 class: LockPlay.Program+RWLSExtList`1[System.Int32] DONE
Wie Sie die Erweiterungen sehen können, indem tatsächlich die Leistung macht WORSE als nur mit lock
(Monitor) .
vergessend entsorgen zu vergessen identisch zu nennen, ist zu entsperren, und in diesem Fall verzögert und nicht-deterministische Finalisierung ist höchst unwahrscheinlich, um die folgenden toten Sperren zu verhindern. Schlimmer noch, es ist (wahrscheinlich) möglich, dass die toten Sperren intermittierend und fast unmöglich zu debuggen sind. Lassen Sie den Finalizer aus und stellen Sie sicher, dass wir einen debugbaren Deadlock erhalten. – wekempf
@wekempf siehe: http://gist.github.com/104477 Nebeneffekt ist, dass Einweg muss eine Klasse sein, damit dies zumindest in DEBUG –
@ Sambo99 funktioniert: Ich kann die Argumentation dort sehen, aber immer noch nicht zustimmen. Im besten Fall haben Sie dead lock in dead lock + eventual assert umgewandelt, ohne zusätzlichen Nutzen (d. H. Es ist nicht einfacher zu debuggen). Alles für einen Fehler, der HOCH wahrscheinlich unwahrscheinlich erscheint (besonders wenn Sie entweder die Namen der Methoden ändern oder sie zu Nicht-Erweiterungsmethoden machen). – wekempf