8

Busco a una implementación de búfer de anillo (o pseudocódigo) en C con las siguientes características:Buscando la derecha aplicación búfer de anillo en C

  • productor múltiple único patrón de consumo (MPSC)
  • bloques de consumo en vacíos
  • productores bloquear en el pleno
  • -cerradura libre (espero alta contención)

Hasta ahora yo' He estado trabajando solo con buffers SPSC, uno por productor, pero me gustaría evitar el giro continuo del consumidor para buscar nuevos datos en todos sus buffers de entrada (y quizás para deshacerme de algunos hilos de cálculo en mi sistema).

Desarrollo para Linux en máquinas Intel.

+0

No sé en qué ambiente se encuentra, pero en Win32 puede usar WaitForMultipleObjects para que el consumidor espere en todas las colas a la vez sin girar. –

+1

Lo siento, no mencioné que trabajo principalmente en Linux – ziu

+1

En caso de que no obtenga una respuesta real, será muy sencillo sincronizar varios almacenamientos intermedios con esto: http://neosmart.net/ blog/2011/waitformultipleobjects-and-win32-events-for-linux-and-read-write-locks-for-windows/ –

Respuesta

2

Creo que tengo lo que estás buscando. Es una implementación de búfer de anillo sin bloqueo que bloquea productor/consumidor. Solo necesita acceder a primitivas atómicas; en este ejemplo usaré las funciones sync de gcc.

Tiene un error conocido: si desborda el búfer en más del 100%, no se garantiza que la cola siga siendo FIFO (aún así los procesará).

Esta aplicación se basa en la lectura/escritura de los elementos de amortiguación como una operación atómica (que está prácticamente garantizado para los punteros)

struct ringBuffer 
{ 
    void** buffer; 
    uint64_t writePosition; 
    size_t size; 
    sem_t* semaphore; 
} 

//create the ring buffer 
struct ringBuffer* buf = calloc(1, sizeof(struct ringBuffer)); 
buf->buffer = calloc(bufferSize, sizeof(void*)); 
buf->size = bufferSize; 
buf->semaphore = malloc(sizeof(sem_t)); 
sem_init(buf->semaphore, 0, 0); 

//producer 
void addToBuffer(void* newValue, struct ringBuffer* buf) 
{ 
    uint64_t writepos = __sync_fetch_and_add(&buf->writePosition, 1) % buf->size; 

    //spin lock until buffer space available 
    while(!__sync_bool_compare_and_swap(&(buf->buffer[writePosition]), NULL, newValue)); 
    sem_post(buf->semaphore); 
}  

//consumer 
void processBuffer(struct ringBuffer* buf) 
{ 
    uint64_t readPos = 0; 
    while(1) 
    { 
     sem_wait(buf->semaphore); 

     //process buf->buffer[readPos % buf->size] 
     buf->buffer[readPos % buf->size] = NULL; 
     readPos++; 
    } 
} 
+1

Este ejemplo es interesante. Déjame ver si entendí bien: el incremento de índice y la escritura están libres de bloqueos, mientras que usas un bloqueo en forma de semáforo para bloquear al consumidor cuando no hay nada que consumir. No entiendo cómo es posible desbordar este búfer, sin embargo. Además, ¿utilizó esta estructura en un sistema donde esperaba un tiempo de centrifugado muy corto? ¿Cómo fue el impacto del ciclo giratorio? – ziu

+0

Puede desbordar el búfer escribiendo datos más rápido de lo que se puede procesar. Finalmente, el índice de escritura aparecerá y "pasará" al lector. En este punto, el escritor tiene que esperar en un bloqueo de giro mientras espera que el lector se ponga al corriente (de lo contrario, estaría sobreescribiendo datos en el búfer). El error ocurre si desborda la cola en más del 100%. En este escenario, tiene más de 1 subproceso esperando en un spinlock para que el búfer esté disponible. No está garantizado cuál de los hilos escribirá primero en la cola. – charliehorse55

+2

¿No sería más sencillo reescribir el ciclo anterior como se muestra a continuación? 'while (1) while (__ sync_bool_compare_and_swap (& (buf-> buffer [writePosition]), NULL, newValue) == false); sem_post (buf-> semáforo); break; } ' – ziu

4

Ver liblfds, un buffer circular PMMC sin bloqueo. No bloqueará en absoluto — las estructuras de datos sin bloqueo no tienden a hacer esto, porque el punto de estar libre de bloqueo es evitar el bloqueo; usted necesita manejar esto, cuando la estructura de datos vuelve a usted con un NULL — devuelve NULL si intenta leer en vacío, pero no coincide con su requisito al escribir lleno; aquí, tirará el elemento más antiguo y te dará eso para tu escritura.

Sin embargo, solo se necesitaría una pequeña modificación para obtener ese comportamiento.

Pero puede haber una solución mejor. La parte más complicada de un ringbuffer es cuando obtiene el elemento anterior más antiguo y lo vuelve a usar. No necesitas esto Creo que podría tomar el buffer circular SPSC con barrera de memoria solo y reescribirlo usando operaciones atómicas. Eso será un lote más eficaz que el buffer de anillo MPMC en liblfds (que es una combinación de una cola y una pila).

+0

Hasta ahora, mi implementación de SPSC es bastante trivial: se basa únicamente en los contadores de posición locales y globales para sincronizar el escritor y el lector (los contadores locales están ahí para agrupar el empuje/extracción de elementos y reducir el uso compartido falso). Las variables de condición están en su lugar para reducir el giro (si no hay datos disponibles, no hay nada más que hacer/si el destino es completo, la contrapresión es inevitable). Sin barreras de memoria adecuadas, mi implementación no funcionará en otra arquitectura. ¿Podría detallar su último punto? Al final, el buffer de anillo siempre será SPSC, ¿verdad? – ziu

+1

Existe un conocido buffer circular SPSC, que se usa, por ejemplo, en el kernel de Linux, que usa solo barreras de memoria, devuelve NULL cuando el buffer está lleno o vacío.Supongo que podría hacerse MPMC mediante el uso de operaciones atómicas. –

Cuestiones relacionadas