Comment initialiser une variable statique dans un contexte multithread?

J’ai pensé à utiliser le mot-clé static dans une fonction comme ceci:

void threadSafeWrite(int *array, int writeIndex, int writeData){ static void *threadLock = Lock_create(); //in my code locks are void* to be cross-platform compatable Lock_aquire(threadLock); array[writeIndex] = writeData; Lock_release(threadLock); } 

En bref, cela semble être un bon moyen de créer une section critique. Ma question est la suivante: comment initialiser le threadLock de manière sécurisée? Le problème avec l’exemple que je crains est que le verrou sera alloué plusieurs fois et chaque thread utilisera un verrou différent. Des idées pour résoudre le problème? On dirait un problème de poulet et d’œufs. Je veux une solution (ou des solutions) qui fonctionne avec les threads pthreads et windows.

EDIT: la raison pour laquelle je souhaite cette fonctionnalité est qu’elle offre un moyen non intrusif de vérifier s’il ya une différence lors de l’exécution d’un code mono-thread ou multithread (destiné à des fins de débogage).

Une approche consiste à utiliser un verrou global pour sérialiser les chemins d’initialisation. Vous aurez cependant besoin d’un wrapper portable sur une barrière de mémoire SMP; La barrière d’acquisition impliquée par le verrou n’est pas suffisante, car elle permet en principe au compilateur et / ou au CPU de mettre en cache les résultats des lectures de mémoire avant l’acquisition du verrou. Voici un exemple:

 Lock global_init_lock; // Should have low contention, as it's only used during startup void somefunc() { static void *data; static long init_flag = 0; if (!init_flag) { // fast non-atomic compare for the fast path global_init_lock.Lock(); read_memory_barrier(); // make sure we re-read init_flag if (!init_flag) data = init_data(); write_memory_barrier(); // make sure data gets committed and is visible to other procs init_flag = 1; global_init_lock.Unlock(); } read_memory_barrier(); // we've seen init_flag = 1, now make sure data is visible // .... } 

Cela dit, je recommande de mettre les verrous avec les données, pas avec les fonctions qui agissent sur les données. Après tout, comment comptez-vous synchroniser des lecteurs comme celui-ci? Que faire si vous souhaitez utiliser des verrous séparés pour des baies séparées ultérieurement? Et si vous voulez écrire d’autres fonctions qui prendront cette serrure plus tard sur la route?

Ne fonctionnera pas car l’initialiseur d’une variable static doit être une constante en C. Un appel de fonction n’est pas une constante. Ceci est différent de C ++, où vous pouvez effectuer un travail static avant que main soit entré. Par exemple, cela ne comstackra pas:

 int deepthought() { return 42; } void ask() { static int answer = deepthought(); } 

L’option la plus simple est de rendre vos verrous globaux et de les initialiser avant de passer en mode MT. Une meilleure option est de les transmettre avec les données qu’ils gardent.

PS: je déconseille d’utiliser un void * pour obtenir un pointeur opaque. Au lieu de cela, implémentez une struct Lock à un élément spécifique à la plate-forme pour la sécurité de type.

Toute l’idée d’une fonction Lock_create est cassée; le fait de le créer nécessiterait une synchronisation, ce que vous ne pouvez pas garantir car vous n’avez pas encore de verrou! N’utilisez pas de pointeurs pour les verrous. Créez plutôt une structure et transmettez l’adresse de cette structure à vos fonctions de locking et de délocking. Ou mieux encore, utilisez ceux fournis par votre système d’exploitation, car il n’y a aucun moyen d’implémenter vous-même le locking en C.

De plus, tout le monde qui a dit que vous deviez rendre le verrou “global” est faux. La scope du niveau de fonction est correcte tant que sa durée de stockage est statique. Il vous suffit d’éliminer la fonction d’allocation Lock_create et d’utiliser un type approprié pour le verrou.

Il existe différentes options:

  • Appelez manuellement le code d’initialisation à un moment sûr (par exemple, avant d’exposer le code à d’autres threads).
  • Utilisez pthread_once() ou équivalent, ce qui garantit que le code d’initialisation est appelé une fois et que tous les appelants suivants voient ses effets.
  • Utilisez une initialisation statique du verrou qui n’implique pas d’appeler une fonction. Par exemple, en utilisant pthreads

     static pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER;