traduccion thread safe concurrentbag concurrent c# multithreading thread-safety interlocked

c# - concurrentbag - Esto es Thread-Safe, ¿verdad?



thread safe list c# (6)

Esto es seguro para hilos, ¿verdad?

Supongamos que MAXIMUM es uno, count es cero y cinco hilos llaman a CheckForWork.

Los cinco hilos podrían verificar que el conteo sea menor que MÁXIMO. El mostrador se incrementaría hasta cinco y comenzarían cinco trabajos.

Eso parece contrario a la intención del código.

Además: el campo no es volátil. Entonces, ¿qué mecanismo garantiza que cualquier hilo leerá un valor actualizado en la ruta sin barrera de memoria? ¡Nada garantiza eso! Solo haces una barrera de memoria si la condición es falsa.

De manera más general: estás haciendo una economía falsa aquí. Al ir con una solución de bloqueo bajo está guardando la docena de nanosegundos que tomaría el bloqueo no previsto. Solo toma la cerradura . Puedes pagar la docena extra de nanosegundos.

Y aún más en general: no escriba código de bloqueo bajo a menos que sea un experto en arquitecturas de procesador y conozca todas las optimizaciones que una CPU puede realizar en rutas de bajo bloqueo . No eres tan experto. Yo tampoco lo soy. Es por eso que no escribo código de bloqueo bajo .

Simplemente revisando ... se está accediendo a la cuenta de forma segura, ¿verdad?

Ambos métodos son accedidos por múltiples hilos.

private int _count; public void CheckForWork() { if (_count >= MAXIMUM) return; Interlocked.Increment(ref _count); Task t = Task.Run(() => Work()); t.ContinueWith(CompletedWorkHandler); } public void CompletedWorkHandler(Task completedTask) { Interlocked.Decrement(ref _count); // Handle errors, etc... }


Definir hilo seguro.

Si quiere asegurarse de que _count nunca será mayor que MÁXIMO, entonces no tuvo éxito.

Lo que debes hacer es bloquear eso también:

private int _count; private object locker = new object(); public void CheckForWork() { lock(locker) { if (_count >= MAXIMUM) return; _count++; } Task.Run(() => Work()); } public void CompletedWorkHandler() { lock(locker) { _count--; } ... }

También es posible que desee echar un vistazo a la clase SemaphoreSlim .


No, if (_count >= MAXIMUM) return; no es seguro para subprocesos

editar: Tendría que bloquear la lectura también, que lógicamente debería ser agrupada con el incremento, así que volvería a escribir como

private int _count; private readonly Object _locker_ = new Object(); public void CheckForWork() { lock(_locker_) { if (_count >= MAXIMUM) return; _count++; } Task.Run(() => Work()); } public void CompletedWorkHandler() { lock(_locker_) { _count--; } ... }


No, lo que tienes no es seguro. El cheque para ver si _count >= MAXIMUM podría competir con la llamada a Interlocked.Increment . _count >= MAXIMUM from another thread. Esto es realmente muy difícil de resolver usando técnicas de bloqueo bajo. Para que esto funcione correctamente, debes hacer que una serie de operaciones aparezcan atómicas sin usar un bloqueo. Esa es la parte difícil. La serie de operaciones en cuestión aquí son:

  • Leer _count
  • Test _count >= MAXIMUM
  • Toma una decisión basada en lo anterior.
  • Incrementar _count dependiendo de la decisión tomada.

Si no haces que los 4 pasos parezcan atómicos, entonces habrá una condición de carrera. El patrón estándar para realizar una operación compleja sin tomar un bloqueo es el siguiente.

public static T InterlockedOperation<T>(ref T location) { T initial, computed; do { initial = location; computed = op(initial); // where op() represents the operation } while (Interlocked.CompareExchange(ref location, computed, initial) != initial); return computed; }

Observe lo que está sucediendo. La operación se realiza repetidamente hasta que la operación ICX determina que el valor inicial no ha cambiado entre el momento en que se leyó por primera vez y el momento en que se intentó cambiarlo. Este es el patrón estándar y la magia todo sucede debido a la CompareExchange (ICX). Tenga en cuenta, sin embargo, que esto no tiene en cuenta el problema ABA . 1

Qué podrías hacer:

Por lo tanto, tomar el patrón anterior e incorporarlo en su código resultaría en esto.

public void CheckForWork() { int initial, computed; do { initial = _count; computed = initial < MAXIMUM ? initial + 1 : initial; } while (Interlocked.CompareExchange(ref _count, computed, initial) != initial); if (replacement > initial) { Task.Run(() => Work()); } }

Personalmente, apostaría en la estrategia de bloqueo bajo por completo. Hay varios problemas con lo que presenté arriba.

  • En realidad, esto podría funcionar más lento que tomar un bloqueo duro. Las razones son difíciles de explicar y están fuera del alcance de mi respuesta.
  • Cualquier desviación de lo que está arriba probablemente hará que el código falle. Sí, realmente es así de frágil.
  • Es difícil de entender Me refiero a mirarlo. Es feo

Lo que debes hacer:

Yendo con la ruta de bloqueo duro su código podría verse así.

private object _lock = new object(); private int _count; public void CheckForWork() { lock (_lock) { if (_count >= MAXIMUM) return; _count++; } Task.Run(() => Work()); } public void CompletedWorkHandler() { lock (_lock) { _count--; } }

Tenga en cuenta que esto es mucho más simple y considerablemente menos propenso a errores. En realidad, puede encontrar que este enfoque (bloqueo fijo) es realmente más rápido que lo que mostré arriba (bloqueo bajo). Una vez más, la razón es complicada y existen técnicas que se pueden usar para acelerar las cosas, pero están fuera del alcance de esta respuesta.

1 El problema de ABA no es realmente un problema en este caso porque la lógica no depende de que _count permanezca sin cambios. Solo importa que su valor sea el mismo en dos puntos en el tiempo independientemente de lo que haya ocurrido en el medio. En otras palabras, el problema puede reducirse a uno en el que pareciera que el valor no cambió aunque en realidad sí lo tenga.


Para eso son Semaphore y SemaphoreSlim :

private readonly SemaphoreSlim WorkSem = new SemaphoreSlim(Maximum); public void CheckForWork() { if (!WorkSem.Wait(0)) return; Task.Run(() => Work()); } public void CompletedWorkHandler() { WorkSem.Release(); ... }


puede hacer lo siguiente si no desea bloquear o mover a un semáforo:

if (_count >= MAXIMUM) return; // not necessary but handy as early return if(Interlocked.Increment(ref _count)>=MAXIMUM+1) { Interlocked.Decrement(ref _count);//restore old value return; } Task.Run(() => Work());

Increment devuelve el valor incrementado en el que puede verificar si _count fue menor que el valor máximo; si la prueba falla, restauro el valor anterior