He estado jugando con colecciones y subprocesos y encontré los ingeniosos métodos de extensión que las personas han creado para facilitar el uso de ReaderWriterLockSlim al permitir el patrón IDisposable.Método de extensión ReaderWriterLockSlim Rendimiento
Sin embargo, creo que me he dado cuenta de que algo en la implementación es un asesino de rendimiento. Me doy cuenta de que no se supone que los métodos de extensión realmente tengan un impacto en el rendimiento, por lo tanto, estoy asumiendo que algo en la implementación es la causa ... ¿la cantidad de estructuras desechables creadas/recolectadas?
Aquí hay algo de código de prueba:
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
Esto es un resultado típico:
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
Como se puede ver, el uso de las extensiones en realidad hace que la actuación peor que simplemente usar lock
(monitor) .
El olvidarse de llamar a Dispose es idéntica a olvidar para desbloquear, y en ese caso retrasa y la finalización no determinista es muy poco probable que ayude a prevenir los bloqueos muertos que siguen. Peor aún, es posible (probable) que haga que los bloqueos muertos sean intermitentes y casi imposibles de depurar. Deje el finalizador y asegúrese de obtener un interbloqueo de depuración. – wekempf
@wekempf ver: http://gist.github.com/104477 efecto secundario es que debe ser una clase descartable para que esto funcione al menos en DEPURTO –
@ sambo99: puedo ver el razonamiento allí, pero todavía no lo hago de acuerdo. En el mejor de los casos, ha convertido dead lock en dead lock + eventual assert, sin ningún beneficio adicional (es decir, no es más fácil de depurar). Todo por un error que parece ALTAMENTE improbable que se me ocurra (especialmente si cambia los nombres de los métodos o los convierte en métodos que no son de extensión). – wekempf