¿Es cierto que C ++ 0x vendrá sin semáforos? Ya hay algunas preguntas sobre Stack Overflow con respecto al uso de semáforos. Los uso (semáforos posix) todo el tiempo para dejar que un hilo espere algún evento en otro hilo:
void thread0(...)
{
doSomething0();
event1.wait();
...
}
void thread1(...)
{
doSomething1();
event1.post();
...
}
Si hiciera eso con un mutex:
void thread0(...)
{
doSomething0();
event1.lock(); event1.unlock();
...
}
void thread1(...)
{
event1.lock();
doSomethingth1();
event1.unlock();
...
}
Problema: es feo y no se garantiza que thread1 bloquee el mutex primero (dado que el mismo thread debería bloquear y desbloquear un mutex, tampoco puede bloquear event1 antes de que se inicien thread0 y thread1).
Entonces, dado que boost tampoco tiene semáforos, ¿cuál es la forma más simple de lograr lo anterior?
Respuestas:
Puede crear fácilmente uno a partir de un mutex y una variable de condición:
fuente
while(!count_)
ciclo.Basado en la respuesta de Maxim Yegorushkin , traté de hacer el ejemplo en estilo C ++ 11.
fuente
cv.wait(lck, [this]() { return count > 0; });
Decidí escribir el semáforo C ++ 11 más robusto / genérico que pude, en el estilo del estándar tanto como pude (tenga en cuenta
using semaphore = ...
que normalmente usaría el nombresemaphore
similar astring
no usar normalmentebasic_string
):fuente
wait_for
ywait_until
con el predicado devuelven un valor booleano (no un `std :: cv_status).std::size_t
no está firmado, por lo que disminuirlo por debajo de cero es UB, y siempre lo será>= 0
. En mi humilde opinióncount
debería ser unint
.de acuerdo con los semáforos posix, agregaría
Y prefiero usar un mecanismo de sincronización a un nivel conveniente de abstracción, en lugar de siempre copiar y pegar una versión unida usando operadores más básicos.
fuente
También puede consultar cpp11-on-multicore : tiene una implementación de semáforo portátil y óptima.
El repositorio también contiene otros objetos de subprocesamiento que complementan el subproceso de c ++ 11.
fuente
Puede trabajar con mutex y variables de condición. Obtiene acceso exclusivo con el mutex, verifique si desea continuar o si necesita esperar al otro extremo. Si necesita esperar, espere en una condición. Cuando el otro hilo determina que puede continuar, señala la condición.
Hay un breve ejemplo en la biblioteca boost :: thread que probablemente puedas copiar (las bibliotecas C ++ 0x y boost thread son muy similares).
fuente
wait()
se traduce a "bloquear, verificar el conteo si no es cero y continuar; si el cero espera en condición" mientraspost
sería "bloquear", contador de incremento, señal si fue 0 "También puede ser útil envoltorio de semáforo RAII en hilos:
Ejemplo de uso en la aplicación multiproceso:
fuente
C ++ 20 finalmente tendrá semáforos -
std::counting_semaphore<max_count>
.Estos tendrán (al menos) los siguientes métodos:
acquire()
(bloqueo)try_acquire()
(sin bloqueo, devuelve inmediatamente)try_acquire_for()
(sin bloqueo, toma una duración)try_acquire_until()
(sin bloqueo, toma un tiempo para dejar de intentarlo)release()
Esto aún no figura en cppreference, pero puede leer estas diapositivas de presentación de CppCon 2019 o ver el video . También está la propuesta oficial P0514R4 , pero no estoy seguro de que sea la versión más actualizada.
fuente
Encontré que shared_ptr y weak_ptr, un largo con una lista, hicieron el trabajo que necesitaba. Mi problema era que tenía varios clientes que querían interactuar con los datos internos de un host. Por lo general, el host actualiza los datos por sí mismo, sin embargo, si un cliente lo solicita, el host debe dejar de actualizarse hasta que ningún cliente acceda a los datos del host. Al mismo tiempo, un cliente puede solicitar acceso exclusivo, de modo que ningún otro cliente, ni el host, puedan modificar los datos del host.
Cómo hice esto fue, creé una estructura:
Cada cliente tendría un miembro de tales:
Luego, el host tendría un miembro weak_ptr para exclusividad y una lista de weak_ptrs para bloqueos no exclusivos:
Hay una función para habilitar el bloqueo y otra función para verificar si el host está bloqueado:
Compruebo los bloqueos en LockUpdate, IsUpdateLocked y periódicamente en la rutina de actualización del host. Probar un bloqueo es tan simple como verificar si los débiles_ptr han expirado, y eliminar cualquier vencido de la lista m_locks (solo hago esto durante la actualización del host), puedo verificar si la lista está vacía; Al mismo tiempo, obtengo desbloqueo automático cuando un cliente restablece el shared_ptr en el que están colgando, lo que también ocurre cuando un cliente se destruye automáticamente.
El efecto general es que, dado que los clientes rara vez necesitan exclusividad (generalmente reservado solo para adiciones y eliminaciones), la mayoría de las veces una solicitud a LockUpdate (falso), es decir, no exclusiva, tiene éxito siempre que (! M_exclusiveLock). Y un LockUpdate (verdadero), una solicitud de exclusividad, tiene éxito solo cuando ambos (! M_exclusiveLock) y (m_locks.empty ()).
Se podría agregar una cola para mitigar entre bloqueos exclusivos y no exclusivos, sin embargo, no he tenido colisiones hasta ahora, por lo que tengo la intención de esperar hasta que eso suceda para agregar la solución (principalmente para tener una condición de prueba del mundo real).
Hasta ahora esto está funcionando bien para mis necesidades; Puedo imaginar la necesidad de expandir esto, y algunos problemas que podrían surgir sobre el uso expandido, sin embargo, esto fue rápido de implementar y requirió muy poco código personalizado.
fuente
En caso de que alguien esté interesado en la versión atómica, aquí está la implementación. El rendimiento se espera mejor que la versión mutex y la variable de condición.
fuente
wait
código tiene que repetirse varias veces. Cuando finalmente se desbloquee, tomará a la madre de todas las ramas mal predichas, ya que la predicción de bucle de la CPU ciertamente predecirá que se repetirá nuevamente. Podría enumerar muchos más problemas con este código.wait
bucle consumirá recursos de microejecución de la CPU a medida que gira. Supongamos que está en el mismo núcleo físico que el hilo que se supone quenotify
lo tiene: ralentizará terriblemente ese hilo.wait
bucle para el mismo semáforo. Ambos escriben a toda velocidad en la misma línea de caché, lo que puede ralentizar otros núcleos al saturar los buses entre núcleos.