2017-02-04 6 views
0

Я просто хочу, чтобы мой код был как можно более простым и надежным потоком.SPSC нить безопасна с забором

С C11 Атомиксом

Что касается части "7.17.4 Заборов" из/IEC 9899/201X проекта

X и Y ISO оба работает на некотором атомном объекте М, таким образом, что A - , секвенированный до X, X изменяет M, Y секвенирован до B, а Y считывает значение, записанное X, или значение, записанное любым побочным эффектом в гипотетической последовательности X , голова, если бы это была операция освобождения ,

Этот код безопасен (с «w_i» как «объект M»)?
Нужно ли «w_i» и «r_i» быть объявлено как _Atomic?
Если только w_i _Atomic, может ли основной поток сохранить старое значение r_i в кеше и считать очередь не полной (пока она заполнена) и записать данные?
Что происходит, если я читаю атом без atom_load?

Я сделал несколько тестов, но все мои попытки, похоже, дают правильные результаты. Однако я знаю, что мои тесты не совсем правильны в отношении многопоточности: я запускаю свою программу несколько раз и смотрю на результат.

Даже если ни w_i не r_i не объявлены как _Atomic, моя программа работает, но только ограждения недостаточны в отношении стандарта C11, правильно?

typedef int rbuff_data_t; 

struct rbuf { 
    rbuff_data_t * buf; 
    unsigned int bufmask; 

    _Atomic unsigned int w_i; 
    _Atomic unsigned int r_i; 
}; 
typedef struct rbuf rbuf_t; 

static inline int 
thrd_tryenq(struct rbuf * queue, rbuff_data_t val) { 
    size_t next_w_i; 

    next_w_i = (queue->w_i + 1) & queue->bufmask; 

    /* if ring full */ 
    if (atomic_load(&queue->r_i) == next_w_i) { 
     return 1; 
    } 

    queue->buf[queue->w_i] = val; 
    atomic_thread_fence(memory_order_release); 
    atomic_store(&queue->w_i, next_w_i); 

    return 0; 
} 

static inline int 
thrd_trydeq(struct rbuf * queue, rbuff_data_t * val) { 
    size_t next_r_i; 

    /*if ring empty*/ 
    if (queue->r_i == atomic_load(&queue->w_i)) { 
     return 1; 
    } 
    next_r_i = (queue->r_i + 1) & queue->bufmask; 
    atomic_thread_fence(memory_order_acquire); 
    *val = queue->buf[queue->r_i]; 
    atomic_store(&queue->r_i, next_r_i); 
    return 0; 
} 

Я называю диссертации функции следующим образом:
Основной нитью епдиеей некоторые данных:

while (thrd_tryenq(thrd_get_queue(&tinfo[tnum]), i)) { 
    usleep(10); 
    continue; 
} 

Других нитей DEQUEUE данных:

static void * 
thrd_work(void *arg) { 
    struct thrd_info *tinfo = arg; 
    int elt; 

    atomic_init(&tinfo->alive, true); 

    /* busy waiting when queue empty */ 
    while (atomic_load(&tinfo->alive)) { 
     if (thrd_trydeq(&tinfo->queue, &elt)) { 
      sched_yield(); 
      continue; 
     } 
     printf("Thread %zu deq %d\n", 
       tinfo->thrd_num, elt); 
    } 

    pthread_exit(NULL); 
} 

С ассемблером заборы

Что касается конкретной платформы x86 с lfence и sfence, Если удалить весь код С11 и просто заменить заборы на

asm volatile ("sfence" ::: "memory"); 

и

asm volatile ("lfence" ::: "memory"); 

(Мое понимание этого макроса: компилятор забор, чтобы предотвратить память доступ для переориентации/оптимизации + аппаратный забор)

Нужно ли, чтобы мои переменные были объявлены как неустойчивые?

Я уже видел этот кольцевой буферный код выше только с этими заборами asm, но без атомных типов, и я был очень удивлен, я хочу знать, был ли этот код верным.

ответ

1

Я просто отвечаю на вопрос о атоллах C11, особенности платформы слишком сложны и должны быть постепенно прекращены.

Синхронизация между потоками в C11 обеспечивается только посредством некоторых системных вызовов (например, для mtx_t) и атомистики. Даже не пытайтесь это делать.

Это говорит о том, что работа по сихронизации через атомизация, то есть видимость побочных эффектов, будет передаваться через видимость эффектов на атомику. Например, для модели простейшей согласованности, последовательной, всякий раз, когда поток T2 видит, что поток изменений T1 воздействует на атомную переменную A, все побочные эффекты до того, как эта модификация в потоке T1 видна для T2.

Так что не все ваши общие переменные должны быть атомарными, вы должны только обеспечить правильное распространение вашего состояния с помощью атома. В этом смысле заборы ничего не покупают, когда вы используете последовательную или согласованную последовательность получения, они только усложняют изображение.

Некоторые более общие замечания:

  • Так как вы, кажется, используют последовательную модель последовательности, которая является по умолчанию, функциональное написание атомарных операций (например atomic_load) является излишним. Просто оценка атомной переменной точно такая же.
  • У меня создалось впечатление, что вы слишком много пытаетесь оптимизировать в начале разработки. Я думаю, вы должны сделать реализацию , для которой вы можете доказать правильность, во-первых. Затем, , если и только если вы заметили проблему с производительностью, вы должны начать думать об оптимизации . Очень маловероятно, что такая атомная структура данных является настоящим узким местом для вашего приложения. У вас должно быть очень большое количество потоков, которые все одновременно забивают вашу бедную маленькую атомную переменную, чтобы увидеть измеримое узкое место здесь.
+0

Спасибо! Если я избегаю заборов, я должен использовать atomic_store_explicit с желаемым порядком памяти? Или я просто напишу: queue-> w_i = next_w_i (это непринужденный порядок, используемый по умолчанию?) В каком случае следует использовать ограждения? – treywelsh

+0

Нет, порядок по умолчанию - последовательная согласованность, максимально возможная. –

Смежные вопросы