Tengo colas sin cierre escritas en C en forma de una lista vinculada que contiene solicitudes de varios hilos publicados y manejados en un único hilo. Después de algunas horas de estrés, termino teniendo el próximo puntero de la última solicitud apuntando hacia sí mismo, lo que crea un ciclo sin fin y bloquea el hilo de manejo.Implementación de cola sin bloqueo termina teniendo un ciclo bajo estrés
La aplicación se ejecuta (y falla) tanto en Linux como en Windows. Estoy depurando en Windows, donde mis mapas COMPARE_EXCHANGE_PTR
a InterlockedCompareExchangePointer.
Este es el código que empuja una solicitud a la cabeza de la lista, y se llama a partir de varios hilos:
void push_request(struct request * volatile * root, struct request * request)
{
assert(request);
do {
request->next = *root;
} while(COMPARE_EXCHANGE_PTR(root, request, request->next) != request->next);
}
Este es el código que recibe una solicitud desde el final de la lista, y es Sólo llamado por un único hilo que los está manejando:
struct request * pop_request(struct request * volatile * root)
{
struct request * volatile * p;
struct request * request;
do {
p = root;
while(*p && (*p)->next) p = &(*p)->next; // <- loops here
request = *p;
} while(COMPARE_EXCHANGE_PTR(p, NULL, request) != request);
assert(request->next == NULL);
return request;
}
tenga en cuenta que no estoy usando un puntero de cola porque quería evitar la complicación de tener que lidiar con el puntero de cola en push_request
. Sin embargo, sospecho que el problema podría estar en la forma en que encuentro el final de la lista.
Hay varios lugares que empujan a una solicitud en la cola, pero todos se ven generaly así:
// device->requests is defined as struct request * volatile requests;
struct request * request = malloc(sizeof(struct request));
if(request) {
// fill out request fields
push_request(&device->requests, request);
sem_post(device->request_sem);
}
El código que controla la solicitud está haciendo más que eso, pero en esencia hace de una loop:
if(sem_wait_timeout(device->request_sem, timeout) == sem_success) {
struct request * request = pop_request(&device->requests);
// handle request
free(request);
}
también acaba de agregar una función que está revisando la lista de duplicados antes y después de cada operación, pero me temo que esta comprobación cambiará la distribución de tal forma que nunca voy a encontrar el punto donde se falla (Estoy esperando que se rompa mientras escribo esto.)
Cuando rompo el programa de colgar, el hilo del manejador gira en pop_request
en la posición marcada. Tengo una lista válida de una o más solicitudes y el último puntero apunta a sí mismo. Las colas de solicitudes suelen ser cortas, nunca he visto más de 10, y solo 1 y 3 las dos veces que pude echar un vistazo a esta falla en el depurador.
Pensé en esto tanto como pude y llegué a la conclusión de que nunca sería capaz de terminar con un ciclo en mi lista a menos que presione la misma solicitud dos veces. Estoy bastante seguro de que esto nunca sucede. También estoy bastante seguro (aunque no completamente) de que no es el ABA problem.
Sé que podría aparecer más de una solicitud al mismo tiempo, pero creo que esto es irrelevante aquí, y nunca lo he visto. (Voy a arreglar esto también)
Pensé largo y tendido acerca de cómo puedo romper mi función, pero no veo una manera de terminar con un ciclo.
Así que la pregunta es: ¿Alguien puede ver cómo se puede romper esto? ¿Alguien puede probar que esto no puede?
Eventualmente voy a resolver esto (tal vez usando un puntero en la cola o alguna otra solución - el bloqueo sería un problema porque los hilos que publican no deberían estar bloqueados, aunque tengo un bloqueo RW) pero me gustaría para asegurarme de que el cambio de la lista realmente resuelve mi problema (en lugar de hacerlo menos probable debido a un calendario diferente).
hace esto bucle infinito cuando se ejecuta con un solo hilo que tanto empuja y hace estallar las solicitudes? Una de las invariantes que tiene es que una lista vacía se representa con 'NULL', pero no es obvio que empiece con una lista vacía establecida en' NULL'. Además, si cree que está enviando solicitudes duplicadas, debe afirmar que 'request-> next == NULL' al comienzo de' push_request() '. – MSN
Sí, estoy comenzando con 'device-> requests' que contienen NULL. 'request-> next' se sobrescribe en la primera línea del ciclo do-while. Con una solicitud duplicada me refiero a 'push_request (request); push_request (solicitud); '. En este caso, terminaría con un bucle. – Fozi