Estoy haciendo un punto de referencia muy tonto en ReaderWriterLock con este código, donde la lectura ocurre 4 veces más a menudo que la escritura:
class Program
{
static void Main()
{
ISynchro[] test = { new Locked(), new RWLocked() };
Stopwatch sw = new Stopwatch();
foreach ( var isynchro in test )
{
sw.Reset();
sw.Start();
Thread w1 = new Thread( new ParameterizedThreadStart( WriteThread ) );
w1.Start( isynchro );
Thread w2 = new Thread( new ParameterizedThreadStart( WriteThread ) );
w2.Start( isynchro );
Thread r1 = new Thread( new ParameterizedThreadStart( ReadThread ) );
r1.Start( isynchro );
Thread r2 = new Thread( new ParameterizedThreadStart( ReadThread ) );
r2.Start( isynchro );
w1.Join();
w2.Join();
r1.Join();
r2.Join();
sw.Stop();
Console.WriteLine( isynchro.ToString() + ": " + sw.ElapsedMilliseconds.ToString() + "ms." );
}
Console.WriteLine( "End" );
Console.ReadKey( true );
}
static void ReadThread(Object o)
{
ISynchro synchro = (ISynchro)o;
for ( int i = 0; i < 500; i++ )
{
Int32? value = synchro.Get( i );
Thread.Sleep( 50 );
}
}
static void WriteThread( Object o )
{
ISynchro synchro = (ISynchro)o;
for ( int i = 0; i < 125; i++ )
{
synchro.Add( i );
Thread.Sleep( 200 );
}
}
}
interface ISynchro
{
void Add( Int32 value );
Int32? Get( Int32 index );
}
class Locked:List<Int32>, ISynchro
{
readonly Object locker = new object();
#region ISynchro Members
public new void Add( int value )
{
lock ( locker )
base.Add( value );
}
public int? Get( int index )
{
lock ( locker )
{
if ( this.Count <= index )
return null;
return this[ index ];
}
}
#endregion
public override string ToString()
{
return "Locked";
}
}
class RWLocked : List<Int32>, ISynchro
{
ReaderWriterLockSlim locker = new ReaderWriterLockSlim();
#region ISynchro Members
public new void Add( int value )
{
try
{
locker.EnterWriteLock();
base.Add( value );
}
finally
{
locker.ExitWriteLock();
}
}
public int? Get( int index )
{
try
{
locker.EnterReadLock();
if ( this.Count <= index )
return null;
return this[ index ];
}
finally
{
locker.ExitReadLock();
}
}
#endregion
public override string ToString()
{
return "RW Locked";
}
}
Pero entiendo que ambos funcionan más o menos de la misma manera:
Locked: 25003ms.
RW Locked: 25002ms.
End
Incluso haciendo la lectura 20 veces más a menudo que la escritura, el rendimiento sigue siendo (casi) el mismo.
¿Estoy haciendo algo mal aquí?
Saludos cordiales.
c#
.net
multithreading
locking
vtortola
fuente
fuente
Respuestas:
En su ejemplo, los sueños significan que generalmente no hay contención. Una cerradura no atendida es muy rápida. Para que esto importa, que se necesita un contendido de bloqueo; si hay escrituras en esa contención, deberían ser más o menos iguales (
lock
pueden ser incluso más rápidas), pero si se trata principalmente de lecturas (con una contención de escritura rara vez), esperaría que elReaderWriterLockSlim
bloqueo supere allock
.Personalmente, prefiero otra estrategia aquí, usar el intercambio de referencias, por lo que las lecturas siempre pueden leer sin verificar / bloquear / etc.Las escrituras hacen su cambio a una copia clonada , luego usan
Interlocked.CompareExchange
para intercambiar la referencia (reaplicando su cambio si otro hilo mutó la referencia en el ínterin).fuente
bool bDone=false; while(!bDone) { object origObj = theObj; object newObj = origObj.DeepCopy(); // then make changes to newObj if(Interlocked.CompareExchange(ref theObj, tempObj, newObj)==origObj) bDone=true; }
Interlocked
y simplemente usando unvolatile
campo y simplemente asignándolo, pero si necesita estar seguro de que su cambio no se perdió por completo, entoncesInterlocked
es idealMis propias pruebas indican que
ReaderWriterLockSlim
tiene aproximadamente 5 veces la sobrecarga en comparación con una normallock
. Eso significa que para que el RWLS supere a un candado antiguo, generalmente se producirían las siguientes condiciones.En la mayoría de las aplicaciones reales, estas dos condiciones no son suficientes para superar esa sobrecarga adicional. En su código específicamente, los bloqueos se mantienen durante un período de tiempo tan corto que la sobrecarga del bloqueo probablemente será el factor dominante. Si moviera esas
Thread.Sleep
llamadas dentro de la cerradura, probablemente obtendría un resultado diferente.fuente
No hay contención en este programa. Los métodos Get y Add se ejecutan en unos pocos nanosegundos. Las probabilidades de que varios hilos lleguen a esos métodos en el momento exacto son extremadamente pequeñas.
Ponga una llamada Thread.Sleep (1) en ellos y elimine el sueño de los hilos para ver la diferencia.
fuente
Edición 2 : Simplemente eliminando las
Thread.Sleep
llamadas deReadThread
yWriteThread
, vi unLocked
rendimiento superiorRWLocked
. Creo que Hans dio en el clavo aquí; sus métodos son demasiado rápidos y no crean contención. Cuando agreguéThread.Sleep(1)
a los métodosGet
yAdd
deLocked
yRWLocked
(y usé 4 hilos de lectura contra 1 hilo de escritura),RWLocked
quité los pantalonesLocked
.Editar : OK, si realmente estuviera pensando cuando publiqué esta respuesta por primera vez, me habría dado cuenta de al menos por qué pusiste las
Thread.Sleep
llamadas allí: estabas tratando de reproducir el escenario de lecturas que ocurren con más frecuencia que escrituras. Esta no es la forma correcta de hacerlo. En cambio, introduciría una sobrecarga adicional a sus métodosAdd
yGet
para crear una mayor posibilidad de contención (como sugirió Hans ), crear más hilos de lectura que de escritura (para asegurar lecturas más frecuentes que escrituras) y eliminar lasThread.Sleep
llamadas deReadThread
yWriteThread
(que en realidad reducir la contención, logrando lo contrario de lo que desea).Me gusta lo que has hecho hasta ahora. Pero aquí hay algunos problemas que veo desde el principio:
Thread.Sleep
llamadas? Estos solo están inflando sus tiempos de ejecución en una cantidad constante, lo que hará que los resultados de rendimiento converjan artificialmente.Thread
objetos en el código que mide suStopwatch
. Ese no es un objeto trivial de crear.Si verá una diferencia significativa una vez que aborde los dos problemas anteriores, no lo sé. Pero creo que deberían abordarse antes de que continúe la discusión.
fuente
Thread
objetos comenzará a ser insignificante.Obtendrá un mejor rendimiento
ReaderWriterLockSlim
que un simple bloqueo si bloquea una parte del código que necesita más tiempo para ejecutarse. En este caso, los lectores pueden trabajar en paralelo. Adquirir unReaderWriterLockSlim
toma más tiempo que ingresar un simpleMonitor
. Verifique miReaderWriterLockTiny
implementación para un bloqueo de lector-escritor que es incluso más rápido que una simple declaración de bloqueo y ofrece una funcionalidad de lector-escritor: http://i255.wordpress.com/2013/10/05/fast-readerwriterlock-for-net/fuente
Consulte este artículo: http://blogs.msdn.com/b/pedram/archive/2007/10/07/a-performance-comparison-of-readerwriterlockslim-with-readerwriterlock.aspx
Es probable que sus horas de sueño sean lo suficientemente largas como para hacer que su bloqueo / desbloqueo sea estadísticamente insignificante.
fuente
Los bloqueos no controvertidos toman el orden de microsegundos para adquirir, por lo que su tiempo de ejecución se verá empequeñecido por sus llamadas a
Sleep
.fuente
A menos que tenga hardware multinúcleo (o al menos el mismo que su entorno de producción planificado), no obtendrá una prueba realista aquí.
Una prueba más sensata sería extender la vida útil de sus operaciones bloqueadas colocando un breve retraso dentro de la cerradura. De esa manera, realmente debería poder contrastar el paralelismo agregado usando
ReaderWriterLockSlim
versus la serialización implícita en basiclock()
.Actualmente, el tiempo que toman sus operaciones que están bloqueadas se pierde en el ruido generado por las llamadas de suspensión que ocurren fuera de las cerraduras. El tiempo total en ambos casos está relacionado principalmente con el sueño.
¿Estás seguro de que tu aplicación del mundo real tendrá el mismo número de lecturas y escrituras?
ReaderWriterLockSlim
es realmente mejor para el caso en el que tiene muchos lectores y escritores relativamente poco frecuentes. 1 hilo de escritor frente a 3 hilos de lector deberían demostrarReaderWriterLockSlim
mejor los beneficios, pero en cualquier caso su prueba debería coincidir con su patrón de acceso esperado en el mundo real.fuente
Supongo que esto se debe al sueño que tienes en tus hilos de lector y escritor.
Su hilo de lectura tiene un sueño de 500tims 50ms, que es 25000 La mayor parte del tiempo está durmiendo
fuente
Cuando tiene significativamente más lecturas que escrituras.
fuente