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