2011-04-21 8 views
8

Tengo un proceso de alta prioridad que necesita pasar datos a un proceso de baja prioridad. Escribí un buffer de anillo básico para manejar el paso de datos:buffer de anillo sin inversión de prioridad

class RingBuffer { 
    public: 
    RingBuffer(int size); 
    ~RingBuffer(); 

    int count() {return (size + end - start) % size;} 

    void write(char *data, int bytes) { 
     // some work that uses only buffer and end 
     end = (end + bytes) % size; 
    } 

    void read(char *data, int bytes) { 
     // some work that uses only buffer and start 
     start = (start + bytes) % size; 
    } 

    private: 
    char *buffer; 
    const int size; 
    int start, end; 
}; 

Aquí está el problema. Supongamos que el proceso de baja prioridad tiene un oráculo que le dice exactamente la cantidad de datos que se deben leer, de modo que no es necesario llamar al count(). Entonces (a menos que me falta algo) no hay problemas de concurrencia. Sin embargo, tan pronto como el hilo de baja prioridad necesite llamar al count() (el hilo de alta prioridad podría querer llamarlo también para verificar si el buffer está demasiado lleno) existe la posibilidad de que el recuento matemático() o la actualización a el final no es atómico, presentando un error.

Podría poner un mutex alrededor de los accesos para iniciar y terminar, pero eso causaría la inversión de prioridad si el hilo de alta prioridad tiene que esperar al bloqueo adquirido por el hilo de baja prioridad.

Podría ser capaz de resolver algo usando operaciones atómicas, pero no conozco una buena biblioteca multiplataforma que las proporcione.

¿Existe un diseño estándar de buffer de anillo que evite estos problemas?

+0

¿Tiene alguna restricciones sobre las plataformas utilizadas? –

+0

@Peter Supongamos que x86 (donde las escrituras IIRC de 32 bits para las direcciones alineadas son atómicas) aunque cuanto más portátil, mejor. – user168715

+0

Aquí hay una cola que opera principalmente sin esperas http://software.intel.com/en-us/articles/single-producer-single-consumer-queue/ –

Respuesta

4

Lo que tienes debe estar bien, siempre y cuando se cumplan las siguientes pautas:

  • sólo un hilo puede hacer escribe.
  • Solo un hilo puede hacer lecturas.
  • Las actualizaciones y los accesos a start y end son atómicos. Esto podría ser automática, por ejemplo, estados de Microsoft:

simple lee y escribe en variables de 32 bits alineados correctamente, son operaciones atómicas. En otras palabras, no terminará con una sola porción de la variable actualizada; todos los bits son actualizados de forma atómica.

  • usted permite que el hecho de que count podría estar fuera de fecha, incluso a medida que el valor. En el hilo de lectura, count devolverá el recuento mínimo de en el que puede confiar; para el hilo de escritura count devolverá el recuento máximo de y el recuento verdadero podría ser menor.
+0

¿Hay algún riesgo de que el compilador divida las actualizaciones en un secuencia de pasos no atómica? Es decir, convertir la última línea de write() en 'end + = bytes; fin% = tamaño; '? – user168715

+0

El compilador puede calcular el RHS de forma no atómica y, dependiendo de su plataforma, incluso puede almacenar un int de forma no atómica (es decir, almacenar un int de 16 bits utilizando dos escrituras de 8 bits en una plataforma de 8 bits). –

+0

@ user168715, si absolutamente no confías en tu compilador para hacer lo correcto, crea una variable temporal 'volátil' para contener el cálculo y cópialo para la actualización. –

2

Boost proporciona un búfer circular, pero no es seguro para subprocesos. Desafortunadamente, no sé de ninguna implementación que sea.

El próximo estándar C++ agrega operaciones atómicas a la biblioteca estándar, por lo que estarán disponibles en el futuro, pero aún no son compatibles con la mayoría de las implementaciones.

No veo ninguna solución multiplataforma para mantener count cuerdo mientras que ambos hilos están escribiendo en él, a menos que implemente el bloqueo.

Normalmente, probablemente use un sistema de mensajes y fuerce el hilo de baja prioridad para solicitar que el hilo de alta prioridad realice actualizaciones, o algo similar.Por ejemplo, si el subproceso de baja prioridad consume 15 bytes, debe solicitar el subproceso de alta prioridad para reducir el recuento en 15.

Esencialmente, limitaría el acceso de 'escritura' al subproceso de alta prioridad y solo permitiría el bajo hilo de prioridad para leer. De esta forma, puede evitar todos los bloqueos, y el hilo de alta prioridad no tendrá que preocuparse por esperar a que una escritura sea completada por el hilo inferior, lo que hace que el hilo de alta prioridad sea verdaderamente de alta prioridad.

1

boost::interprocess ofrece multiplataforma funciones atómicas en boost/interprocess/detail/atomic.hpp