Entre (p)select et (p)poll il y a une différence assez subtile :
Pour select, vous devez initialiser et remplir les vilains bitmaps fd_set à chaque fois avant d'appeler select car select les modifie sur place de manière "destructrice". (le sondage distingue le .events
et .revents
membres en struct pollfd
).
Après la sélection, le bitmap entier est souvent scanné (par des personnes/code) pour les événements même si la plupart des fds ne sont même pas regardés.
Troisièmement, le bitmap ne peut traiter que des fds dont le nombre est inférieur à une certaine limite (implémentations contemporaines :quelque part entre 1024..4096), ce qui l'exclut dans les programmes où des fds élevés peuvent être facilement atteints (même si ces programmes sont susceptibles de utilisez déjà epoll à la place).
Je suggérerais en commençant la comparaison avec select()
contre poll()
. Linux fournit également à la fois pselect()
et ppoll()
; et le const sigset_t *
supplémentaire argument de pselect()
et ppoll()
(vs select()
et poll()
) a le même effet sur chaque "p-variante", pour ainsi dire. Si vous n'utilisez pas de signaux, vous n'avez aucune course contre laquelle vous protéger, donc la question de base est vraiment l'efficacité et la facilité de programmation.
En attendant, il y a déjà une réponse stackoverflow.com ici :quelles sont les différences entre poll et select.
Quant à la course :une fois que vous commencez à utiliser des signaux (pour quelque raison que ce soit), vous apprendrez qu'en général, un gestionnaire de signaux doit simplement définir une variable de type volatile sig_atomic_t
pour indiquer que le signal a été détecté. La raison fondamentale en est que de nombreux appels de bibliothèque ne sont pas réentrants et qu'un signal peut être délivré alors que vous êtes "au milieu" d'une telle routine. Par exemple, simplement imprimer un message dans une structure de données de style flux telle que stdout
(C) ou cout
(C++) peut entraîner des problèmes de réentrance.
Supposons que vous ayez un code qui utilise un volatile sig_atomic_t flag
variable, peut-être pour attraper SIGINT
, quelque chose comme ça (voir aussi http://pubs.opengroup.org/onlinepubs/007904975/functions/sigaction.html):
volatile sig_atomic_t got_interrupted = 0;
void caught_signal(int unused) {
got_interrupted = 1;
}
...
struct sigaction sa;
sa.sa_handler = caught_signal;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
if (sigaction(SIGINT, &sa, NULL) == -1) ... handle error ...
...
Maintenant, dans le corps principal de votre code, vous voudrez peut-être "exécuter jusqu'à ce qu'il soit interrompu":
while (!got_interrupted) {
... do some work ...
}
C'est bien jusqu'à ce que vous commenciez à avoir besoin de faire des appels qui attendent une entrée/sortie, comme select
ou poll
. L'action "wait" doit attendre cette E/S, mais elle aussi doit attendre un SIGINT
couper la parole. Si vous écrivez simplement :
while (!got_interrupted) {
... do some work ...
result = select(...); /* or result = poll(...) */
}
alors il est possible que l'interruption se produise juste avant vous appelez select()
ou poll()
, plutôt qu'après. Dans ce cas, vous avez été interrompu et la variable got_interrupted
se prépare, mais après cela, vous commencez à attendre. Vous auriez dû cocher le got_interrupted
variable avant de commencer à attendre, pas après.
Vous pouvez essayer d'écrire :
while (!got_interrupted) {
... do some work ...
if (!got_interrupted)
result = select(...); /* or result = poll(...) */
}
Cela réduit la "fenêtre de course", car maintenant vous détecterez l'interruption si elle se produit pendant que vous êtes dans le code "faire du travail" ; mais il y a toujours une course, car l'interruption peut se produire juste après vous testez la variable, mais juste avant le select-or-poll.
La solution consiste à rendre la séquence "tester, puis attendre" "atomique", en utilisant les propriétés de blocage de signal de sigprocmask
(ou, dans le code fileté POSIX, pthread_sigmask
):
sigset_t mask, omask;
...
while (!got_interrupted) {
... do some work ...
/* begin critical section, test got_interrupted atomically */
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
if (sigprocmask(SIG_BLOCK, &mask, &omask))
... handle error ...
if (got_interrupted) {
sigprocmask(SIG_SETMASK, &omask, NULL); /* restore old signal mask */
break;
}
result = pselect(..., &omask); /* or ppoll() etc */
sigprocmask(SIG_SETMASK, &omask, NULL);
/* end critical section */
}
(le code ci-dessus n'est en fait pas si génial, il est structuré à des fins d'illustration plutôt que d'efficacité - il est plus efficace de faire la manipulation du masque de signal légèrement différemment et de placer les tests "s'est interrompu" différemment).
Jusqu'à ce que vous commenciez réellement à avoir besoin d'attraper SIGINT
, cependant, vous n'avez qu'à comparer select()
et poll()
(et si vous commencez à avoir besoin d'un grand nombre de descripteurs, certaines des choses basées sur des événements comme epoll()
est plus efficace que l'un ou l'autre).