Combien de lecteurs simultanés un pthread_rwlock peut-il avoir?

J’ai une application multi-thread qui crée 48 threads qui ont tous besoin d’accéder à un atsortingbut commun (stl :: map). La carte ne sera écrite que lorsque les threads démarrent, et le rest du temps, la carte sera lue. Cela semble être le cas parfait pour un pthread_rw_lock, et tout semble fonctionner correctement.

J’ai rencontré un défaut de segmentation complètement indépendant et j’ai commencé à parsingr le kernel. En utilisant gdb, j’ai exécuté les info threads commande et j’ai été très surpris des résultats. J’ai observé que plusieurs threads étaient en train de lire la carte comme prévu, mais la partie la plus étrange est que plusieurs threads ont été bloqués dans pthread_rwlock_rdlock () en attente sur le rw_lock.

Voici la trace de la stack pour un thread qui attend sur le verrou:

 #0 0xffffe430 in __kernel_vsyscall () #1 0xf76fe159 in __lll_lock_wait () from /lib/libpthread.so.0 #2 0xf76fab5d in pthread_rwlock_rdlock () from /lib/libpthread.so.0 #3 0x0804a81a in DiameterServiceSingleton::getDiameterService(void*) () 

Avec autant de threads, il est difficile de dire combien d’entre eux lisaient et combien étaient bloqués, mais je ne comprends pas pourquoi des threads seraient bloqués en attente de lecture, étant donné que d’autres threads sont déjà en train de lire.

Voici donc ma question: pourquoi certains threads sont-ils bloqués en attente de lecture d’un rw_lock, alors que d’autres threads en lisent déjà? Il semble que le nombre de threads pouvant être lus simultanément soit limité.

J’ai regardé les fonctions de pthread_rwlock_attr_t et je n’ai rien vu.

Le système d’exploitation est Linux, SUSE 11.

Voici le code associé:

 { pthread_rwlock_init(&serviceMapRwLock_, NULL); } // This method is called for each request processed by the threads Service *ServiceSingleton::getService(void *serviceId) { pthread_rwlock_rdlock(&serviceMapRwLock_); ServiceMapType::const_iterator iter = serviceMap_.find(serviceId); bool notFound(iter == serviceMap_.end()); pthread_rwlock_unlock(&serviceMapRwLock_); if(notFound) { return NULL; } return iter->second; } // This method is only called when the app is starting void ServiceSingleton::addService(void *serviceId, Service *service) { pthread_rwlock_wrlock(&serviceMapRwLock_); serviceMap_[serviceId] = service; pthread_rwlock_unlock(&serviceMapRwLock_); } 

Mettre à jour:

Comme mentionné dans les commentaires de MarkB, si j’avais paramétré pthread_rwlockattr_getkind_np () pour donner la priorité aux auteurs et qu’un écrivain bloqué attendait, alors le comportement observé aurait un sens. Mais, j’utilise la valeur par défaut qui, selon moi, est de donner la priorité aux lecteurs. Je viens de vérifier qu’il n’y a pas de threads bloqués en attente d’écriture. Je mets également à jour le code comme suggéré par @Shahbaz dans les commentaires et obtient les mêmes résultats.

Vous avez simplement observé les problèmes de performances inhérents à l’acquisition de verrous. Cela prend du temps, et vous avez juste réussi à attraper ces fils au milieu. Cela est particulièrement vrai lorsque l’opération protégée par la serrure a une durée très courte.

Edit: En lisant la source, glibc utilise lll_lock pour protéger les sections critiques au sein de ses propres structures de données de bibliothèque pthread. Le pthread_rwlock_rdlock vérifie plusieurs indicateurs et incrémente les compteurs, il fait donc tout cela en maintenant un verrou. Une fois celles-ci terminées, le verrou est libéré avec lll_unlock .

Pour démontrer, j’ai mis en œuvre une routine courte qui dort après avoir acquis la rwlock . Le thread principal attend leur fin. Mais avant d’attendre, il imprime la concurrence obtenue par les threads.

 enum { CONC = 50 }; pthread_rwlock_t rwlock; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; unsigned count; void *routine(void *arg) { int *fds = static_cast(arg); pthread_rwlock_rdlock(&rwlock); pthread_mutex_lock(&mutex); ++count; if (count == CONC) pthread_cond_signal(&cond); pthread_mutex_unlock(&mutex); sleep(5); pthread_rwlock_unlock(&rwlock); pthread_t self = pthread_self(); write(fds[1], &self, sizeof(self)); return 0; } 

Et le thread principal attend que le compteur atteigne 50:

 int main() { int fds[2]; pipe(fds); pthread_rwlock_init(&rwlock, 0); pthread_mutex_lock(&mutex); for (int i = 0; i < CONC; i++) { pthread_t tid; pthread_create(&tid, NULL, routine, fds); } while (count < CONC) pthread_cond_wait(&cond, &mutex); pthread_mutex_unlock(&mutex); std::cout << "count: " << count << std::endl; for (int i = 0; i < CONC; i++) { pthread_t tid; read(fds[0], &tid, sizeof(tid)); pthread_join(tid, 0); } pthread_rwlock_destroy(&rwlock); pthread_exit(0); } 

Edit: simplifie l'exemple en utilisant le support du thread C ++ 11:

 enum { CONC = 1000 }; std::vector threads; pthread_rwlock_t rwlock; std::mutex mutex; std::condition_variable cond; unsigned count; void *routine(int self) { pthread_rwlock_rdlock(&rwlock); { std::unique_lock lk(mutex); if (++count == CONC) cond.notify_one(); } sleep(5); pthread_rwlock_unlock(&rwlock); return 0; } int main() { pthread_rwlock_init(&rwlock, 0); { std::unique_lock lk(mutex); for (int i = 0; i < CONC; i++) { threads.push_back(std::thread(routine, i)); } cond.wait(lk, [](){return count == CONC;}); } std::cout << "count: " << count << std::endl; for (int i = 0; i < CONC; i++) { threads[i].join(); } pthread_rwlock_destroy(&rwlock); pthread_exit(0); } 

En guise de note, le code affiché ci-dessus est cassé. Vous ne pouvez pas accéder à iter-> second hors de la section rw_lock’d, car dès que vous déverrouillez le rw_lock, un écrivain peut supprimer n’importe quel élément de la carte, ce qui invalide tout iterator.

Je sais que vous ne le faites pas dans votre cas puisque vous n’écrivez rien après le début de l’exécution du programme, mais cela vaut quand même la peine d’être mentionné.

En outre, comme le comportement de votre description semble être sérialisé (les auteurs écrivent sur la carte au début, les lecteurs lisent une carte “en lecture seule” à partir de maintenant), vous devriez probablement l’écrire comme ceci:

 int writerDoneWithMap = 0; // pthread_cond & mutex init here // The writer write to the map here // Then they signal the reader that they are done with it while (!__sync_bool_compare_and_swap(&writerDoneWithMap, 1, writerDoneWithMap)); pthread_cond_broadcast here // The readers will then simply do this: while (!writerDoneWithMap) { // pthread_cond_wait here } // Read the data without locks. 

Le code ci-dessus évite tout blocage sur les lecteurs si le rédacteur a fini de remplir la carte, et dans le cas contraire, vous avez recours aux techniques typiques de pthread_cond / mutex. Le code ci-dessus est correct si et seulement si vous avez des lecteurs THEN THEN (mais c’est ce que vous avez dit), sinon cela échouera.