DPDK реализация кольцевого буфера MPSC

#c11 #lock-free #circular-buffer #stdatomic #dpdk

#c11 #без блокировки #кольцевой буфер #stdatomic #dpdk

Вопрос:

При выполнении реализации MPSC DPDK (с несколькими производителями и одним потребителем) API кольцевого буфера, я нашел код для перемещения заголовка производителя для вставки новых элементов в кольцевой буфер. Функция заключается в следующем :

     static __rte_always_inline unsi&ned int
    __rte_rin&_move_prod_head(struct rte_rin& *r, unsi&ned int is_sp,
            unsi&ned int n, enum rte_rin&_queue_behavior behavior,
            uint32_t *old_head, uint32_t *new_head,
            uint32_t *free_entries)
    {
        const uint32_t capacity = r-&&t;capacity;
        uint32_t cons_tail;
        unsi&ned int max = n;
        int success;
    
        *old_head = __atomic_load_n(amp;r-&&t;prod.head, __ATOMIC_RELAXED);
        do {
            /* Reset n to the initial burst count */
            n = max;
    
            /* Ensure the head is read before tail */
            __atomic_thread_fence(__ATOMIC_ACQUIRE);
    
            /* load-acquire synchronize with store-release of ht-&&t;tail
             * in update_tail.
             */
            cons_tail = __atomic_load_n(amp;r-&&t;cons.tail,
                        __ATOMIC_ACQUIRE);
    
            /* The subtraction is done between two unsi&ned 32bits value
             * (the result is always modulo 32 bits even if we have
             * *old_head &&t; cons_tail). So 'free_entries' is always between 0
             * and capacity (which is < size).
             */
            *free_entries = (capacity   cons_tail - *old_head);
    
            /* check that we have enou&h room in rin& */
            if (unlikely(n &&t; *free_entries))
                n = (behavior == RTE_RING_QUEUE_FIXED) ?
                        0 : *free_entries;
    
            if (n == 0)
                return 0;
    
            *new_head = *old_head   n;
            if (is_sp)
                r-&&t;prod.head = *new_head, success = 1;
            else
                /* on failure, *old_head is updated */
                success = __atomic_compare_exchan&e_n(amp;r-&&t;prod.head,
                        old_head, *new_head,
                        0, __ATOMIC_RELAXED,
                        __ATOMIC_RELAXED);
        } while (unlikely(success == 0));
        return n;
    }
  

Загрузка и сравнение head производителя выполняется с использованием __АТОМАРНОГО_упорядочения памяти. Разве это не проблема, когда несколько производителей из разных потоков производят в очередь. Или я что-то упускаю?

https://doc.dpdk.or&/&uides/pro&_&uide/rin&_lib.html описывает базовый механизм, который DPDK использует для реализации кольцевого буфера.

Комментарии:

1. Как вы думаете, к какой проблеме это может привести? Получение / освобождение позволило бы производителям синхронизироваться друг с другом, но они не хотят читать данные друг друга, отличные от head. расслабленные атомы все еще атомарны.

2. @PeterCordes, не будет ли использование упрощенного упорядочения означать, что каждый производитель может видеть разное значение head, поскольку при использовании упрощенного упорядочения памяти в потоках не применяется конкретное упорядочение памяти.

3. Нет, порядок изменения для каждого объекта гарантированно будет восприниматься различными потоками последовательно.

4. @Prateek: Наличие «неправильного» старого значения перед попыткой CAS просто означает, что она завершается неудачей, поэтому вы повторяете попытку. На 100% нормально выполнять расслабленную загрузку перед центром сертификации, и это не нужно заказывать wrt. любые другие загрузки или сохранения в другие объекты.

5. @PeterCordes применимо ли это, даже если сам CAS выполняется с использованием упрощенного упорядочения памяти?