Кольцевой буфер без ожидания

У меня есть сценарий, в котором у меня может быть несколько производителей, но один потребитель кольцевого буфера. вот исходный код:

typedef struct ring_buffer_t {
    uint8_t *data;
    uint32_t element_size;
    uint32_t element_count;
    uint32_t head;
    uint32_t tail;
    uint32_t mask;
} ring_buffer;

uint32_t ring_buffer_put(ring_buffer *buf, void *data_element) {
    int i;
    uint32_t status = 0;
    uint8_t *buf_pointer;
    uint8_t *element = (uint8_t *) data_element;

    if (buf && data_element) {

        buf_pointer = &buf->data[(buf->head & buf->mask) * buf->element_size];
        for (i = 0; i < buf->element_size; ++i) {
            buf_pointer[i] = element[i];
        }

        status = 1;
        __sync_fetch_and_add(&buf->head, 1);

    }

    return status;
}

uint32_t ring_buffer_size(ring_buffer *buf) {

    return buf->head - buf->tail;
}

uint32_t ring_buffer_empty(ring_buffer *buf) {

    return (ring_buffer_size(buf) == 0);
}

void *ring_buffer_get(ring_buffer *buf) {
    void *element;
    //preserve the invariant that tail is always <= head
    if (ring_buffer_empty(buf)) {
        return 0;
    }

    element = &buf->data[(buf->tail & buf->mask) * buf->element_size];
    __sync_fetch_and_add(&buf->tail, 1);
    return element;
}

Заявления о проблеме позволяют мне перезаписывать старые записи, например. вот почему я использую и и нет проверки заполнения буфера при вводе - я просто перезаписываю самую старую запись. Однако мне трудно рассуждать, является ли это потокобезопасным. Как указывалось ранее, важно, чтобы это было без ожидания (соответственно без блокировки по определению), поскольку производители не могут позволить себе блокировку. Размер всегда будет степенью 2, поэтому & во время индексации работает.

Любой вклад будет оценен?


person LordDoskias    schedule 22.11.2013    source источник
comment
Я не понимаю, как это может быть потокобезопасным, __sync_fetch_and_add действительно является атомарным, но что предотвращает состояние гонки? Я не вижу его.   -  person jim mcnamara    schedule 22.11.2013
comment
Единственная реализация кольцевого буфера без ожидания — это та, которая вызывает exit(1) при переполнении буфера.   -  person Hans Passant    schedule 22.11.2013
comment
Технически буфер никогда не может быть заполнен, потому что перезаписывать старые записи вполне нормально.   -  person LordDoskias    schedule 22.11.2013


Ответы (1)


Нет, ваша реализация не является потокобезопасной.

Если два потока put одновременно выполняют запись элементов, они могут закончить запись в одной и той же позиции или даже половине записи в одной позиции, а другой половине — в следующей позиции.

person Klas Lindbäck    schedule 22.11.2013
comment
Это действительно небезопасно для потоков для случая с несколькими производителями и одним потребителем. Хотя это будет работать для одного потребителя, одного производителя. Чтобы сделать его потокобезопасным для SCMP, мне пришлось бы прибегнуть к чему-то похожему на то, что делает разрушитель blog.codeaholics.org/2011/the-disruptor-lock-free-publishing - person LordDoskias; 22.11.2013