lock_guard - mutex c++
Carga masiva de la CPU usando std:: lock(c++ 11) (4)
Mis esfuerzos recientes para implementar un administrador de subprocesos / mutex terminaron en una carga de CPU del 75% (4 núcleos), mientras que los cuatro subprocesos en ejecución estaban en suspensión o esperando a que se desbloqueara un mutex.
La clase específica es demasiado grande para publicarla aquí por completo, pero podría reducir la causa a la adquisición de dos mutexes a salvo de un punto muerto seguro.
std::unique_lock<std::mutex> lock1( mutex1, std::defer_lock );
std::unique_lock<std::mutex> lock2( mutex2, std::defer_lock );
std::lock( lock1, lock2 );
Otra parte de la clase utiliza un std::condition_variable
with wait()
y notify_one()
en mutex1
para que un código se ejecute de forma selectiva al mismo tiempo.
El simple cambio a
std::unique_lock<std::mutex> lock1( mutex1 );
std::unique_lock<std::mutex> lock2( mutex2 );
redujo el uso de la CPU al 1-2% normal.
No puedo creer, la función std::lock()
es tan ineficiente. ¿Podría ser esto un error en g ++ 4.6.3?
editar: (ejemplo)
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
#include <condition_variable>
std::mutex mutex1, mutex2;
std::condition_variable cond_var;
bool cond = false, done = false;
using namespace std::chrono_literals;
void Take_Locks()
{
while( !done )
{
std::this_thread::sleep_for( 1s );
std::unique_lock<std::mutex> lock1( mutex1, std::defer_lock );
std::unique_lock<std::mutex> lock2( mutex2, std::defer_lock );
std::lock( lock1, lock2 );
std::this_thread::sleep_for( 1s );
lock1.unlock();
lock2.unlock();
}
}
void Conditional_Code()
{
std::unique_lock<std::mutex> lock1( mutex1, std::defer_lock );
std::unique_lock<std::mutex> lock2( mutex2, std::defer_lock );
std::lock( lock1, lock2 );
std::cout << "t4: waiting /n";
while( !cond )
cond_var.wait( lock1 );
std::cout << "t4: condition met /n";
}
int main()
{
std::thread t1( Take_Locks ), t2( Take_Locks ), t3( Take_Locks );
std::thread t4( Conditional_Code );
std::cout << "threads started /n";
std::this_thread::sleep_for( 10s );
std::unique_lock<std::mutex> lock1( mutex1 );
std::cout << "mutex1 locked /n" ;
std::this_thread::sleep_for( 5s );
std::cout << "setting condition/notify /n";
cond = true;
cond_var.notify_one();
std::this_thread::sleep_for( 5s );
lock1.unlock();
std::cout << "mutex1 unlocked /n";
std::this_thread::sleep_for( 6s );
done = true;
t4.join(); t3.join(); t2.join(); t1.join();
}
Como dice la documentación, los objetos están bloqueados por una serie no especificada de llamadas a bloquear, intentar bloquear, desbloquear . Sencillamente, no hay forma de que pueda ser eficiente si los mutex se mantienen en otros hilos durante un período de tiempo significativo. No hay forma de que la función pueda esperar sin girar.
En mi máquina, el siguiente código se imprime 10 veces por segundo y consume casi 0 cpu porque la mayoría de las veces el hilo está inactivo o bloqueado en una exclusión mutua bloqueada:
#include <chrono>
#include <thread>
#include <mutex>
#include <iostream>
using namespace std::chrono_literals;
std::mutex m1;
std::mutex m2;
void
f1()
{
while (true)
{
std::unique_lock<std::mutex> l1(m1, std::defer_lock);
std::unique_lock<std::mutex> l2(m2, std::defer_lock);
std::lock(l1, l2);
std::cout << "f1 has the two locks/n";
std::this_thread::sleep_for(100ms);
}
}
void
f2()
{
while (true)
{
std::unique_lock<std::mutex> l2(m2, std::defer_lock);
std::unique_lock<std::mutex> l1(m1, std::defer_lock);
std::lock(l2, l1);
std::cout << "f2 has the two locks/n";
std::this_thread::sleep_for(100ms);
}
}
int main()
{
std::thread t1(f1);
std::thread t2(f2);
t1.join();
t2.join();
}
Salida de muestra:
f1 has the two locks
f2 has the two locks
f1 has the two locks
...
Estoy ejecutando esto en OS X y la aplicación Activity Monitor dice que este proceso está usando 0.1% de CPU. La máquina es un Intel Core i5 (4 núcleos).
Me complace ajustar este experimento de cualquier manera para intentar crear un bloqueo de tiempo real o un uso excesivo de la CPU.
Actualizar
Si este programa utiliza una CPU excesiva en su plataforma, intente cambiarlo a call ::lock()
lugar, donde se define con:
template <class L0, class L1>
void
lock(L0& l0, L1& l1)
{
while (true)
{
{
std::unique_lock<L0> u0(l0);
if (l1.try_lock())
{
u0.release();
break;
}
}
std::this_thread::yield();
{
std::unique_lock<L1> u1(l1);
if (l0.try_lock())
{
u1.release();
break;
}
}
std::this_thread::yield();
}
}
Me gustaría saber si eso hizo alguna diferencia para usted, gracias.
Actualización 2
Después de un largo retraso, he escrito un primer borrador de un documento sobre este tema. El documento compara 4 formas diferentes de hacer este trabajo. Contiene software que puede copiar y pegar en su propio código y probarse usted mismo (¡e informe de lo que encuentre!):
La función no miembro std::lock()
puede causar un problema de Live-lock o una degradación del rendimiento, solo garantiza " Never Dead-lock ".
Si puede determinar el "orden de bloqueo (jerarquía de bloqueo)" de múltiples exclusión mutuas por diseño, es preferible no usar std::lock()
genérico, sino bloquear cada exclusión en un orden predeterminado.
Consulte Adquirir varios bloqueos sin interbloqueo para obtener más detalles.
Primero quiero agradecer por todas las respuestas.
Durante el trabajo en un código de ejemplo, que reproduce el efecto, encontré la fuente de problemas.
La parte condicional bloquea ambos mutex, mientras que usa solo uno para la función std::condition_variable::wait()
.
Pero aún me pregunto qué sucede detrás de la escena, que produce una carga de CPU tan alta.