microseconds c++ multithreading c++11 standard-library

microseconds - usleep() c++



C++ 11 Comportamiento de espera del subproceso: std:: this_thread:: yield() vs. std:: this_thread:: sleep_for(std:: chrono:: milisegundos(1)) (3)

Al escribir el código C ++ específico de Microsoft, me dijeron que escribir Sleep(1) es mucho mejor que Sleep(0) para spinlocking, debido a que Sleep(0) usará más tiempo de CPU, además, solo cede si hay es otro hilo de igual prioridad que espera para ejecutarse.

Sin embargo, con la biblioteca de subprocesos de C ++ 11, no hay mucha documentación (al menos que haya podido encontrar) sobre los efectos de std::this_thread::yield() vs. std::this_thread::sleep_for( std::chrono::milliseconds(1) ) ; el segundo es ciertamente más detallado, pero ¿son ambos igual de eficientes para un spinlock, o sufren potencialmente los mismos errores que afectaron a Sleep(0) vs. Sleep(1) ?

Un bucle de ejemplo donde sea std::this_thread::yield() o std::this_thread::sleep_for( std::chrono::milliseconds(1) ) sería aceptable:

void SpinLock( const bool& bSomeCondition ) { // Wait for some condition to be satisfied while( !bSomeCondition ) { /*Either std::this_thread::yield() or std::this_thread::sleep_for( std::chrono::milliseconds(1) ) is acceptable here.*/ } // Do something! }


Acabo de hacer una prueba con Visual Studio 2013 en Windows 7, 2.8GHz Intel i7, optimizaciones de modo de lanzamiento predeterminadas.

sleep_for (distinto de cero) aparece inactivo durante un mínimo de alrededor de un milisegundo y no toma recursos de CPU en un bucle como:

for (int k = 0; k < 1000; ++k) std::this_thread::sleep_for(std::chrono::nanoseconds(1));

Este bucle de 1,000 horas de sueño toma aproximadamente 1 segundo si usa 1 nanosegundo, 1 microsegundo o 1 milisegundo. Por otro lado, el rendimiento () toma alrededor de 0.25 microsegundos cada uno pero girará la CPU al 100% para el hilo:

for (int k = 0; k < 4,000,000; ++k) (commas added for clarity) std::this_thread::yield();

std :: this_thread :: sleep_for ((std :: chrono :: nanoseconds (0)) parece ser el mismo que el rendimiento () (la prueba no se muestra aquí).

En comparación, el bloqueo de un atomic_flag para un spinlock toma aproximadamente 5 nanosegundos. Este bucle es de 1 segundo:

std::atomic_flag f = ATOMIC_FLAG_INIT; for (int k = 0; k < 200,000,000; ++k) f.test_and_set();

Además, un mutex toma aproximadamente 50 nanosegundos, 1 segundo para este bucle:

for (int k = 0; k < 20,000,000; ++k) std::lock_guard<std::mutex> lock(g_mutex);

Basado en esto, probablemente no dudaría en poner un rendimiento en el spinlock, pero es casi seguro que no usaría sleep_for. Si crees que tus bloqueos girarán mucho y estás preocupado por el consumo de la CPU, cambiaría a std :: mutex si eso fuera práctico en tu aplicación. Esperemos que los días de rendimiento realmente malo en std :: mutex en Windows hayan quedado atrás.


El Estándar es un tanto confuso aquí, ya que una implementación concreta estará influenciada en gran medida por las capacidades de programación del sistema operativo subyacente.

Dicho esto, puedes asumir con seguridad algunas cosas en cualquier SO moderno:

  • yield abandonará el intervalo de tiempo actual y volverá a insertar el hilo en la cola de programación. La cantidad de tiempo que expira hasta que el subproceso se ejecuta de nuevo suele depender totalmente del programador. Tenga en cuenta que el Estándar habla del rendimiento como una oportunidad para reprogramación . Por lo tanto, una implementación es completamente libre de regresar de un rendimiento inmediatamente si así lo desea. Un rendimiento nunca marcará un hilo como inactivo, por lo que un hilo que gira en un rendimiento siempre producirá una carga del 100% en un núcleo. Si no hay otros subprocesos listos, es probable que pierda a lo sumo el resto del intervalo de tiempo actual antes de que sea programado nuevamente.
  • sleep_* bloqueará el hilo durante al menos la cantidad de tiempo solicitada. Una implementación puede convertir un sleep_for(0) en un yield . El sleep_for(1) en la otra mano enviará su hilo en suspensión. En lugar de volver a la cola de programación, el subproceso va primero a una cola diferente de subprocesos inactivos. Solo después de que haya transcurrido el tiempo solicitado, el programador considerará volver a insertar el hilo en la cola de programación. La carga producida por un pequeño sueño todavía será muy alta. Si el tiempo de espera solicitado es más pequeño que el intervalo de tiempo del sistema, puede esperar que el hilo solo se salte un intervalo de tiempo (es decir, un rendimiento para liberar el intervalo de tiempo activo y luego se omita el siguiente), lo que aún llevará a una carga de la CPU. cerca o incluso igual al 100% en un núcleo.

Unas pocas palabras sobre cuál es mejor para el bloqueo de giro. El bloqueo de giro es una herramienta de elección cuando se espera poca o ninguna contención en la cerradura. Si en la gran mayoría de los casos espera que el bloqueo esté disponible, los cierres giratorios son una solución barata y valiosa. Sin embargo, tan pronto como tengas contención, te costarán los spin-locks. Si está preocupado por si el rendimiento o el reposo son la mejor solución, los spin-locks son la herramienta incorrecta para el trabajo . Deberías usar un mutex en su lugar.

Para un bloqueo de giro, el caso de que realmente tenga que esperar al bloqueo debe considerarse excepcional. Por lo tanto, es perfectamente correcto rendirse aquí: expresa la intención claramente y perder el tiempo de CPU nunca debe ser una preocupación en primer lugar.


Si está interesado en la carga de la CPU mientras usa el rendimiento (es muy malo, excepto en un caso) (solo se ejecuta su aplicación y es consciente de que básicamente se comen todos sus recursos)

Aquí hay más explicación:

  • la ejecución del rendimiento en bucle asegurará que la CPU liberará la ejecución del subproceso, aún así, si el sistema intenta volver al subproceso, simplemente repetirá la operación de rendimiento. Esto puede hacer que el subproceso use la carga completa del 100% del núcleo de la CPU.
  • ejecutar sleep() o sleep_for() también es un error, esto bloqueará la ejecución de subprocesos, pero tendrá algo como el tiempo de espera en la CPU. No se equivoque, esta CPU funciona pero en la prioridad más baja posible. Si bien de alguna manera funciona con ejemplos de uso simple (la CPU completamente cargada en modo de espera () es la mitad de mala que la del procesador en funcionamiento completamente cargado), si desea garantizar la responsabilidad de la aplicación, le gustaría algo como el tercer ejemplo:
  • ¡combinatorio! :

    std::chrono::milliseconds duration(1); while (true) { if(!mutex.try_lock()) { std::this_thread::yield(); std::this_thread::sleep+for(duration); continue; } return; }

Algo así se asegurará, la CPU producirá tan rápido como se ejecutará esta operación, y también sleep_for () asegurará que la CPU esperará un tiempo antes de intentar ejecutar la siguiente iteración. Este tiempo puede, por supuesto, ser dinámico (o estático) ajustado a sus necesidades

saludos :)