Je veux juste mon code aussi simple que possible et thread sécurisé.SPSC thread sûr avec des clôtures
Avec C11 atomics
En ce qui concerne une partie "7.17.4 clôtures" de la norme ISO/IEC 9899/201X projet
X et Y, les deux fonctionnant sur un certain objet M atomique, de telle sorte que A est séquence avant X, X modifie M, Y est séquencé avant B, et Y lit la valeur écrite par X ou une valeur écrite par un effet secondaire dans la séquence de libération hypothétique se dirigerait si elle était une opération de libération . Ce thread de code est-il sûr (avec "w_i" comme "objet M")?
Est-ce que "w_i" et "r_i" doivent être déclarés comme _Atomic?
Si seulement w_i est _Atomic, le thread principal peut-il conserver une ancienne valeur de r_i dans le cache et considérer la file d'attente comme incomplète (lorsqu'elle est pleine) et écrire des données?
Que se passe-t-il si je lis un atome sans atomic_load?
J'ai fait quelques tests mais toutes mes tentatives semblent donner les bons résultats. Cependant, je sais que mes tests ne sont pas vraiment corrects concernant le multithread: je lance mon programme plusieurs fois et regarde le résultat.
Même si ni w_i not r_i n'est déclaré comme _Atomic, mon programme fonctionne, mais seules les clôtures ne sont pas suffisantes en ce qui concerne la norme C11, n'est-ce pas?
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;
}
J'appeler des fonctions de thèses comme suit:
fil conducteur file d'attente le quelques données:
while (thrd_tryenq(thrd_get_queue(&tinfo[tnum]), i)) {
usleep(10);
continue;
}
fils Autres données 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);
}
Avec des clôtures asm
En ce qui concerne un x86 plate-forme spécifique avec lfence et sfence, Si je supprime tout le code C11 et juste remplacer les clôtures par
asm volatile ("sfence" ::: "memory");
et
asm volatile ("lfence" ::: "memory");
(Ma compréhension de ces macro est: clôture du compilateur pour éviter que la mémoire accès à réorganiser/optimiser + clôture matérielle)
mes variables doivent-elles être déclarées comme volatiles par exemple?
J'ai déjà vu ce code tampon en anneau ci-dessus avec seulement ces clôtures ASM mais sans types atomiques et j'ai été vraiment surpris, je veux savoir si ce code était correct.
Merci beaucoup! Si j'évite les clôtures, je dois utiliser atomic_store_explicit avec l'ordre de mémoire voulu? Ou je voudrais juste écrire: queue-> w_i = next_w_i (c'est l'ordre relaxé utilisé par défaut?) Dans quel cas dois-je utiliser des clôtures? – treywelsh
Non, l'ordre par défaut est la cohérence séquentielle, la plus forte possible. –