Utilisation de verrous en lecture / écriture
Dans de nombreuses situations, les données sont lues plus souvent que modifiées ou écrites.
Dans ces cas, vous pouvez autoriser les unités d'exécution à lire simultanément tout en maintenant le verrou et n'autoriser qu'une seule unité d'exécution à maintenir le verrou lorsque les données sont modifiées. C'est le cas d'un verrou à lecteur unique (ou verrou en lecture / écriture). Un verrou en lecture / écriture est acquis pour la lecture ou l'écriture, puis est libéré. L'unité d'exécution qui acquiert le verrou en lecture-écriture doit être celle qui le libère.
Objet attributs de lecture / écriture
La sous-routine pthread_rwlockattr_init initialise un objet d'attributs de verrou en lecture-écriture (attr). La valeur par défaut de tous les attributs est définie par l'implémentation. Des résultats inattendus peuvent se produire si la sous-routine pthread_rwlockattr_init spécifie un objet d'attributs de verrouillage en lecture / écriture déjà initialisé.
pthread_rwlockattr_t attr;et :pthread_rwlockattr_init(&attr);Une fois qu'un objet d'attributs de verrou en lecture / écriture est utilisé pour initialiser un ou plusieurs verrous en lecture / écriture, toute fonction qui affecte l'objet d'attributs (y compris la destruction) n'affecte pas les verrous en lecture / écriture précédemment initialisés.
La sous-routine pthread_rwlockattr_destroy détruit un objet d'attributs de verrouillage en lecture / écriture. Des résultats inattendus peuvent se produire si l'objet est utilisé avant d'être réinitialisé par un autre appel à la sous-routine pthread_rwlockattr_init . Une implémentation peut amener la sous-routine pthread_rwlockattr_destroy à définir l'objet référencé par l'objet attr sur une valeur non valide.
Création et destruction de verrous en lecture / écriture
La sous-routine pthread_rwlock_init initialise le verrou en lecture / écriture référencé par l'objet rwlock avec les attributs référencés par l'objet attr . Si l'objet attr est NULL, les attributs de verrou en lecture / écriture par défaut sont utilisés ; l'effet est identique à la transmission de l'adresse d'un objet d'attributs de verrou en lecture / écriture par défaut. Une fois l'initialisation réussie, l'état du verrou en lecture / écriture est initialisé et déverrouillé. Une fois initialisé, le verrou peut être utilisé plusieurs fois sans être réinitialisé. Des résultats inattendus peuvent se produire si l'appel de la sous-routine pthread_rwlock_init est appelé en spécifiant un verrou en lecture / écriture déjà initialisé, ou si un verrou en lecture / écriture est utilisé sans avoir été initialisé au préalable.
Si la sous-routine pthread_rwlock_init échoue, l'objet rwlock n'est pas initialisé et le contenu n'est pas défini.
- Si le verrou est utilisé avant d'être réinitialisé par un autre appel à la sous-routine pthread_rwlock_init .
- Une implémentation peut amener la sous-routine pthread_rwlock_destroy à définir l'objet référencé par l'objet rwlock sur une valeur non valide. Des résultats inattendus peuvent se produire si pthread_rwlock_destroy est appelé lorsqu'une unité d'exécution contient l'objet rwlock .
- La tentative de destruction d'un verrou en lecture / écriture non initialisé entraîne des résultats inattendus. Un objet verrou en lecture / écriture détruit peut être réinitialisé à l'aide de la sous-routine pthread_rwlock_init . Des résultats inattendus peuvent se produire si l'objet verrou en lecture / écriture est référencé après sa destruction.
pthread_rwlock_t rwlock1 = PTHREAD_RWLOCK_INITIALIZER;L'effet est similaire à l'initialisation dynamique en utilisant un appel à la sous-routine pthread_rwlock_init avec le paramètre attr défini sur NULL, sauf qu'aucune vérification des erreurs n'est effectuée. Par exemple :pthread_rwlock_init(&rwlock2, NULL);pthread_rwlock_init(&rwlock, &attr);Verrouillage d'un objet de verrouillage en lecture / écriture pour la lecture
La sous-routine pthread_rwlock_rdlock applique un verrou en lecture au verrou en lecture / écriture référencé par l'objet rwlock . L'unité d'exécution appelante acquiert le verrou en lecture si un programme d'écriture ne le détient pas et si aucun programme d'écriture n'est bloqué sur le verrou. Il n'est pas précisé si l'unité d'exécution appelante acquiert le verrou lorsqu'un programme d'écriture ne le détient pas et que des programmes d'écriture attendent le verrou. Si un programme d'écriture détient le verrou, l'unité d'exécution appelante n'acquiert pas le verrou en lecture. Si le verrou en lecture n'est pas acquis, l'unité d'exécution appelante ne renvoie pas l'appel pthread_rwlock_rdlock tant qu'elle ne peut pas acquérir le verrou. Les résultats ne sont pas définis si l'unité d'exécution appelante détient un verrou en écriture sur l'objet rwlock au moment de l'appel.
Une unité d'exécution peut détenir plusieurs verrous en lecture simultanés sur l'objet rwlock (c'est-à-dire qu'elle peut appeler la sous-routine pthread_rwlock_rdlock n fois). Si tel est le cas, l'unité d'exécution doit effectuer des déverrouillages correspondants (c'est-à-dire qu'elle doit appeler la sous-routine pthread_rwlock_unlock n fois).
La sous-routine pthread_rwlock_tryrdlock applique un verrou en lecture similaire à la sous-routine pthread_rwlock_rdlock , à l'exception de l'échec de la sous-routine si une unité d'exécution détient un verrou en écriture sur l'objet rwlock ou si des programmes d'écriture sont bloqués sur l'objet rwlock . Les résultats ne sont pas définis si l'une de ces fonctions est appelée avec un verrou en lecture / écriture non initialisé.
Si un signal est délivré à une unité d'exécution en attente d'un verrou en lecture / écriture pour lecture, au retour du gestionnaire de signaux, l'unité d'exécution reprend l'attente du verrou en lecture / écriture pour lecture comme si elle n'était pas interrompue.
Verrouillage d'un objet de verrouillage en lecture / écriture pour l'écriture
La sous-routine pthread_rwlock_wrlock applique un verrou en écriture au verrou en lecture / écriture référencé par l'objet rwlock . L'unité d'exécution appelante acquiert le verrou en écriture si aucune autre unité d'exécution (lecteur ou programme d'écriture) ne détient le verrou en lecture / écriture sur l'objet rwlock . Sinon, l'unité d'exécution ne revient pas de l'appel pthread_rwlock_wrlock tant qu'elle ne peut pas acquérir le verrou. Les résultats ne sont pas définis si l'unité d'exécution appelante détient le verrou en lecture / écriture (qu'il s'agisse d'un verrou en lecture ou en écriture) au moment de l'appel.
La sous-routine pthread_rwlock_trywrlock applique un verrou en écriture similaire à la sous-routine pthread_rwlock_wrlock , à l'exception de l'échec de la fonction si une unité d'exécution détient actuellement rwlock pour la lecture ou l'écriture. Les résultats ne sont pas définis si l'une de ces fonctions est appelée avec un verrou en lecture / écriture non initialisé.
Si un signal est délivré à une unité d'exécution en attente d'un verrou en lecture / écriture pour l'écriture, à son retour du gestionnaire de signaux, l'unité d'exécution reprend l'attente du verrou en lecture / écriture pour l'écriture comme si elle n'était pas interrompue.
Préférence d'une unité d'exécution de programme d'écriture sur une unité d'exécution de programme de lecture
La sous-routine pthread_rwlock_attr_setfavorwriters_np peut être utilisée par une application pour initialiser les attributs d'un verrou en lecture / écriture. Vous pouvez spécifier la bibliothèque pthread pour définir la priorité de la planification des unités d'exécution qui requièrent le verrou en lecture / écriture en mode écriture. Lorsque la bibliothèque pthread planifie les unités d'écriture (unités d'exécution qui écrivent des données) pour obtenir le verrou en lecture / écriture en mode écriture, la bibliothèque pthread ne prend pas en charge la récursivité par les unités d'exécution qui détiennent un verrou en lecture / écriture en mode lecture. Des résultats inattendus peuvent se produire lorsque les unités d'exécution détiennent plusieurs fois un verrou en lecture / écriture en mode lecture.
La sous-routine pthread_rwlock_attr_getfavorwriters_np renvoie la préférence en cours définie dans la structure d'attribut de verrouillage en lecture / écriture. Par défaut, les unités d'exécution de lecture sont prioritaires sur les unités d'exécution d'écriture pour obtenir un verrou en lecture / écriture.
Exemples de programmes de verrouillage en lecture / écriture
Les exemples de programme suivants montrent comment utiliser des sous-routines de verrouillage. Pour exécuter ces programmes, vous avez besoin du fichier check.h et du fichier makefile.
#include stdio.h
#include stdio.h
#include stdio.h
#include stdio.h
/* Simple function to check the return code and exit the program
if the function call failed
*/
static void compResults(char *string, int rc) {
if (rc) {
printf("Error on : %s, rc=%d",
string, rc);
exit(EXIT_FAILURE);
}
return;
}CC_R = xlc_r
TARGETS = test01 test02 test03
OBJS = test01.o test02.o test03.o
SRCS = $(OBJS:.o=.c)
$(TARGETS): $(OBJS)
$(CC_R) -o $@ $@.o
clean:
rm $(OBJS) $(TARGETS)
run:
test01
test02
test03Exemple d'unité d'exécution unique
L'exemple suivant utilise la sous-routine pthread_rwlock_tryrdlock avec une seule unité d'exécution. Pour un exemple d'utilisation de la sous-routine pthread_rwlock_tryrdlock avec plusieurs unités d'exécution, voir Multiple-Thread Example.
Example: test01.c
#define _MULTI_THREADED
#include pthread.h
#include stdio.h
#include "check.h"
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
void *rdlockThread(void *arg)
{
int rc;
int count=0;
printf("Entered thread, getting read lock with mp wait\n");
Retry:
rc = pthread_rwlock_tryrdlock(&rwlock);
if (rc == EBUSY) {
if (count >= 10) {
printf("Retried too many times, failure!\n");
exit(EXIT_FAILURE);
}
++count;
printf("Could not get lock, do other work, then RETRY...\n");
sleep(1);
goto Retry;
}
compResults("pthread_rwlock_tryrdlock() 1\n", rc);
sleep(2);
printf("unlock the read lock\n");
rc = pthread_rwlock_unlock(&rwlock);
compResults("pthread_rwlock_unlock()\n", rc);
printf("Secondary thread complete\n");
return NULL;
}
int main(int argc, char **argv)
{
int rc=0;
pthread_t thread;
printf("Enter test case - %s\n", argv[0]);
printf("Main, get the write lock\n");
rc = pthread_rwlock_wrlock(&rwlock);
compResults("pthread_rwlock_wrlock()\n", rc);
printf("Main, create the try read lock thread\n");
rc = pthread_create(&thread, NULL, rdlockThread, NULL);
compResults("pthread_create\n", rc);
printf("Main, wait a bit holding the write lock\n");
sleep(5);
printf("Main, Now unlock the write lock\n");
rc = pthread_rwlock_unlock(&rwlock);
compResults("pthread_rwlock_unlock()\n", rc);
printf("Main, wait for the thread to end\n");
rc = pthread_join(thread, NULL);
compResults("pthread_join\n", rc);
rc = pthread_rwlock_destroy(&rwlock);
compResults("pthread_rwlock_destroy()\n", rc);
printf("Main completed\n");
return 0;
}
La sortie de cet exemple de programme sera similaire à la suivante:
Enter test case - ./test01
Main, get the write lock
Main, create the try read lock thread
Main, wait a bit holding the write lock
Entered thread, getting read lock with mp wait
Could not get lock, do other work, then RETRY...
Could not get lock, do other work, then RETRY...
Could not get lock, do other work, then RETRY...
Could not get lock, do other work, then RETRY...
Could not get lock, do other work, then RETRY...
Main, Now unlock the write lock
Main, wait for the thread to end
unlock the read lock
Secondary thread complete
Main completed
Exemple à unités d'exécution multiples
L'exemple suivant utilise la sous-routine pthread_rwlock_tryrdlock avec plusieurs unités d'exécution. Pour un exemple d'utilisation de la sous-routine pthread_rwlock_tryrdlock avec une seule unité d'exécution, voir Single-Thread Example.
Example: test02.c
#define _MULTI_THREADED
#include pthread.h
#include stdio.h
#include "check.h"
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
void *wrlockThread(void *arg)
{
int rc;
int count=0;
printf("%.8x: Entered thread, getting write lock\n",
pthread_self());
Retry:
rc = pthread_rwlock_trywrlock(&rwlock);
if (rc == EBUSY) {
if (count >= 10) {
printf("%.8x: Retried too many times, failure!\n",
pthread_self());
exit(EXIT_FAILURE);
}
++count;
printf("%.8x: Go off an do other work, then RETRY...\n",
pthread_self());
sleep(1);
goto Retry;
}
compResults("pthread_rwlock_trywrlock() 1\n", rc);
printf("%.8x: Got the write lock\n", pthread_self());
sleep(2);
printf("%.8x: Unlock the write lock\n",
pthread_self());
rc = pthread_rwlock_unlock(&rwlock);
compResults("pthread_rwlock_unlock()\n", rc);
printf("%.8x: Secondary thread complete\n",
pthread_self());
return NULL;
}
int main(int argc, char **argv)
{
int rc=0;
pthread_t thread, thread2;
printf("Enter test case - %s\n", argv[0]);
printf("Main, get the write lock\n");
rc = pthread_rwlock_wrlock(&rwlock);
compResults("pthread_rwlock_wrlock()\n", rc);
printf("Main, create the timed write lock threads\n");
rc = pthread_create(&thread, NULL, wrlockThread, NULL);
compResults("pthread_create\n", rc);
rc = pthread_create(&thread2, NULL, wrlockThread, NULL);
compResults("pthread_create\n", rc);
printf("Main, wait a bit holding this write lock\n");
sleep(1);
printf("Main, Now unlock the write lock\n");
rc = pthread_rwlock_unlock(&rwlock);
compResults("pthread_rwlock_unlock()\n", rc);
printf("Main, wait for the threads to end\n");
rc = pthread_join(thread, NULL);
compResults("pthread_join\n", rc);
rc = pthread_join(thread2, NULL);
compResults("pthread_join\n", rc);
rc = pthread_rwlock_destroy(&rwlock);
compResults("pthread_rwlock_destroy()\n", rc);
printf("Main completed\n");
return 0;
}
La sortie de cet exemple de programme sera similaire à la suivante:
Enter test case - ./test02
Main, get the write lock
Main, create the timed write lock threads
Main, wait a bit holding this write lock
00000102: Entered thread, getting write lock
00000102: Go off an do other work, then RETRY...
00000203: Entered thread, getting write lock
00000203: Go off an do other work, then RETRY...
Main, Now unlock the write lock
Main, wait for the threads to end
00000102: Got the write lock
00000203: Go off an do other work, then RETRY...
00000203: Go off an do other work, then RETRY...
00000102: Unlock the write lock
00000102: Secondary thread complete
00000203: Got the write lock
00000203: Unlock the write lock
00000203: Secondary thread complete
Main completed
Exemple de verrou en lecture / écriture
L'exemple suivant utilise la sous-routine pthread_rwlock_rdlock pour implémenter des verrous en lecture / écriture:
Example: test03.c
#define _MULTI_THREADED
#include pthread.h
#include stdio.h
#include "check.h"
pthread_rwlock_t rwlock;
void *rdlockThread(void *arg)
{
int rc;
printf("Entered thread, getting read lock\n");
rc = pthread_rwlock_rdlock(&rwlock);
compResults("pthread_rwlock_rdlock()\n", rc);
printf("got the rwlock read lock\n");
sleep(5);
printf("unlock the read lock\n");
rc = pthread_rwlock_unlock(&rwlock);
compResults("pthread_rwlock_unlock()\n", rc);
printf("Secondary thread unlocked\n");
return NULL;
}
void *wrlockThread(void *arg)
{
int rc;
printf("Entered thread, getting write lock\n");
rc = pthread_rwlock_wrlock(&rwlock);
compResults("pthread_rwlock_wrlock()\n", rc);
printf("Got the rwlock write lock, now unlock\n");
rc = pthread_rwlock_unlock(&rwlock);
compResults("pthread_rwlock_unlock()\n", rc);
printf("Secondary thread unlocked\n");
return NULL;
}
int main(int argc, char **argv)
{
int rc=0;
pthread_t thread, thread1;
printf("Enter test case - %s\n", argv[0]);
printf("Main, initialize the read write lock\n");
rc = pthread_rwlock_init(&rwlock, NULL);
compResults("pthread_rwlock_init()\n", rc);
printf("Main, grab a read lock\n");
rc = pthread_rwlock_rdlock(&rwlock);
compResults("pthread_rwlock_rdlock()\n",rc);
printf("Main, grab the same read lock again\n");
rc = pthread_rwlock_rdlock(&rwlock);
compResults("pthread_rwlock_rdlock() second\n", rc);
printf("Main, create the read lock thread\n");
rc = pthread_create(&thread, NULL, rdlockThread, NULL);
compResults("pthread_create\n", rc);
printf("Main - unlock the first read lock\n");
rc = pthread_rwlock_unlock(&rwlock);
compResults("pthread_rwlock_unlock()\n", rc);
printf("Main, create the write lock thread\n");
rc = pthread_create(&thread1, NULL, wrlockThread, NULL);
compResults("pthread_create\n", rc);
sleep(5);
printf("Main - unlock the second read lock\n");
rc = pthread_rwlock_unlock(&rwlock);
compResults("pthread_rwlock_unlock()\n", rc);
printf("Main, wait for the threads\n");
rc = pthread_join(thread, NULL);
compResults("pthread_join\n", rc);
rc = pthread_join(thread1, NULL);
compResults("pthread_join\n", rc);
rc = pthread_rwlock_destroy(&rwlock);
compResults("pthread_rwlock_destroy()\n", rc);
printf("Main completed\n");
return 0;
}
La sortie de cet exemple de programme sera similaire à la suivante:
$ ./test03
Enter test case - ./test03
Main, initialize the read write lock
Main, grab a read lock
Main, grab the same read lock again
Main, create the read lock thread
Main - unlock the first read lock
Main, create the write lock thread
Entered thread, getting read lock
got the rwlock read lock
Entered thread, getting write lock
Main - unlock the second read lock
Main, wait for the threads
unlock the read lock
Secondary thread unlocked
Got the rwlock write lock, now unlock
Secondary thread unlocked
Main completed