Comment utiliser fork () dans unix? Pourquoi pas quelque chose de la fourche de forme(pointerToFunctionToRun)?
j'ai du mal à comprendre comment utiliser le fork()
D'Unix . J'ai l'habitude, quand j'ai besoin de parallélisation, de créer des threads dans mon application. Il est toujours quelque chose de la forme
CreateNewThread(MyFunctionToRun());
void myFunctionToRun() { ... }
maintenant, en apprenant sur Unix fork()
, on m'a donné des exemples de la forme:
fork();
printf("%dn", 123);
, dans lequel le code après la fourche est "divisé". Je ne comprends pas comment fork() peut être utile. Pourquoi ne pas fork() est similaire syntaxe à la CreateNewThread () ci-dessus, où vous lui passez l'adresse d'une fonction que vous voulez exécuter?
pour accomplir quelque chose de similaire à CreateNewThread (), je devrais être créatif et faire quelque chose comme
//pseudo code
id = fork();
if (id == 0) { //im the child
FunctionToRun();
} else { //im the parent
wait();
}
peut-être que le problème est que je suis tellement habitué à frayer des fils de la façon .NET que je ne peux pas penser clairement à ce sujet. Ce qui me manque ici? Quels sont les avantages de fork()
par rapport à CreateNewThread()
?
PS: Je sais que fork()
donnera naissance à un nouveau processus , tandis que CreateNewThread()
donnera naissance à un nouveau fil .
Merci
8 réponses
fork()
dit "copier le processus actuel de l'état dans un nouveau processus et de commencer à fonctionner à partir d'ici."Parce que le code est alors exécuté en deux processus, il retourne en fait deux fois: une fois dans le processus parent (où il renvoie l'identificateur du processus enfant) et une fois dans l'enfant (où il renvoie zéro).
Il y a beaucoup de restrictions sur ce qu'il est possible d'appeler dans le processus de l'enfant après fork()
(voir ci-dessous). L'espoir est que l'appel fork()
était la première partie d'un nouveau processus qui exécutait un nouvel exécutable avec son propre état. La deuxième partie de ce processus est un appel à execve()
ou l'une de ses variantes, qui spécifie le chemin vers un exécutable à charger dans le processus en cours, les arguments à fournir à ce processus, et les variables d'environnement pour entourer ce processus. (Il n'y a rien pour vous empêcher de ré-exécuter l'exécutable en cours d'exécution et de fournir un drapeau qui le rendra reprends là où le parent s'est arrêté, si c'est ce que tu veux vraiment.)
UNIX fork()-exec()
la danse est à peu près l'équivalent de la Windows CreateProcess()
. Une nouvelle fonction lui ressemble encore plus: posix_spawn()
.
comme exemple pratique de l'utilisation de fork()
, considérons un shell, tel que bash
. fork()
est utilisé tout le temps par un shell de commande. Quand vous dites à l'interpréteur de commandes de lancer un programme (tel que echo "hello world"
), il se bifurque et puis execs de ce programme. Un pipeline est un ensemble de procédés en fourche avec stdout
et stdin
montés de façon appropriée par la société mère entre fork()
et exec()
.
si vous voulez créer un nouveau thread, vous devez utiliser la bibliothèque de threads Posix. Vous créez un nouveau fil Posix (pthread) en utilisant pthread_create()
. Votre exemple CreateNewThread()
ressemblerait à ceci:
#include <pthread.h>
/* Pthread functions are expected to accept and return void *. */
void *MyFunctionToRun(void *dummy __unused);
pthread_t thread;
int error = pthread_create(&thread,
NULL/*use default thread attributes*/,
MyFunctionToRun,
(void *)NULL/*argument*/);
avant que les fils soient disponibles, fork()
était la chose la plus proche fournie par UNIX à multithreading. Maintenant que les threads sont disponibles, l'utilisation de fork()
est presque entièrement limitée à la génération d'un nouveau processus pour exécuter un exécutable différent.
ci-dessous: les restrictions sont parce que fork()
est antérieur au multithreading, donc seul le thread qui appelle fork()
continue à s'exécuter dans le processus enfant. Par POSIX :
un procédé doit être créé avec un seul fil. Si un processus multi-threadé appelle fork (), le nouveau processus doit contenir une réplique du thread appelant et de tout son espace d'adresse, incluant éventuellement les états des mutex et d'autres ressources. Par conséquent, pour éviter les erreurs, le processus child ne peut exécuter que des opérations async-signal-safe jusqu'à ce que l'une des fonctions exec soit appelée. Les manipulateurs de fourches [THR] [Option Start] peuvent être établis au moyen de la fonction pthread_atfork() afin de maintenir l'application des invariants d'un fork() appelle. [Fin De L'Option]
lorsque l'application appelle fork() à partir d'un gestionnaire de signal et que l'un des gestionnaires de fork enregistrés par pthread_atfork () appelle une fonction qui n'est pas asynch-signal-safe, le comportement n'est pas défini.
parce que n'importe quelle fonction de bibliothèque que vous appelez pourrait avoir engendré un thread en votre nom, l'hypothèse paranoïaque est que vous êtes toujours limité à exécuter async du signal de la sécurité de l'exploitation dans le processus de l'enfant entre un appel fork()
et exec()
.
Histoire mise à part, il existe certaines différences fondamentales entre les processus et les fils en ce qui concerne la propriété des ressources et le temps de vie.
lorsque vous bifurquez, le nouveau processus occupe un espace mémoire complètement séparé. C'est une distinction très importante de la création d'un nouveau thread. Dans les applications multi-threadées, vous devez considérer comment vous accédez et manipulez les ressources partagées. Les traitements qui ont été bifurqués doivent explicitement partager les ressources en utilisant les moyens d'inter-processus tels que la mémoire partagée, les pipes, les appels de procédure à distance, les sémaphores, etc.
une autre différence est que les enfants de fork()'ed peuvent survivre à leur parent, où comme tous les fils meurent lorsque le processus se termine.
dans une architecture client-serveur où une disponibilité très, très longue est attendue, l'utilisation de fork() plutôt que de créer des threads pourrait être une stratégie valide pour combattre les fuites de mémoire. Plutôt que de se préoccuper de nettoyer les fuites de mémoire dans votre threads, vous venez de bifurquer un nouveau processus enfant pour traiter chaque demande de client, puis tuer l'enfant lorsque c'est fait. La seule source de fuites de mémoire serait alors le processus parent qui distribue des événements.
une analogie: vous pouvez penser aux fils de fraie comme l'ouverture des onglets à l'intérieur d'une fenêtre de navigateur simple, tandis que bifurcation est comme l'ouverture de fenêtres de navigateur séparées.
il serait plus valide de demander pourquoi CreateNewThread ne renvoie pas simplement un ID de thread comme fork()
le fait... après tout, fork()
a créé un précédent. Votre opinion est colorée par le fait que vous avez vu l'un avant l'autre. Prenez un peu de recul et considérez que fork()
fait double emploi avec le processus et continue l'exécution... quel meilleur endroit qu'à la prochaine instruction? Pourquoi compliquer les choses en ajoutant une fonction appel dans le marché (et puis un ce qui ne prend nul*)?
votre commentaire à Mike dit "Je ne peux pas comprendre dans quels contextes vous voulez l'utiliser.". Fondamentalement, vous l'utilisez quand vous voulez:
- exécuter un autre processus en utilisant la famille de fonctions exec
- faire un certain traitement parallèle indépendamment (en termes d'utilisation de la mémoire, la manipulation du signal, les ressources, la sécurité)
- par exemple, chaque processus peut avoir des limites intrusives du nombre de descripteurs de fichiers qu'il peut gérer, ou sur un système 32 bits - la quantité de mémoire: un deuxième processus peut partager le travail tout en obtenant ses propres ressources
BTW / utiliser UNIX/Linux ne signifie pas que vous devez abandonner les threads pour les processus de fork()ing... vous pouvez utiliser pthread_create () et releated functions si vous êtes plus à l'aise avec le paradigme du threading.
laisser la différence entre un processus de fraie et un fil mis de côté pour une seconde: fondamentalement, fork() est une primitive plus fondamentale. Tandis que SpawnNewThread doit faire un travail de fond pour obtenir le compteur de programme au bon endroit, fork ne fait pas un tel travail, il copie juste (ou copie virtuellement) votre mémoire de programme et continue le compteur.
Fork est avec nous depuis très, très, très longtemps. Fork a été pensé avant que l'idée de "démarrer un fil tournant une fonction particulière" était une lueur dans l'oeil de n'importe qui.
les gens n'utilisent pas fork
parce que c'est "mieux", nous l'utilisons parce que c'est la seule et unique fonction de création de processus en mode utilisateur non privilégié qui fonctionne à travers toutes les variations de Linux. Si vous voulez créer un processus , vous devez appeler fork
. Et, pour certaines fins, un processus est ce que vous avez besoin, pas d'un fil.
vous pourriez envisager de faire des recherches sur les premiers documents sur le sujet.
Il est intéressant de noter que les traitement n'est pas exactement la même que de multiples threading . Le nouveau processus créé par fork partage très peu de contexte avec l'ancien, ce qui est très différent du cas des threads.
donc, regardons l'unixy thread système: pthread_create
a la sémantique similaire à CreateNewThread
.
ou, pour faire demi - tour, regardons le windows (ou java ou un autre système qui fait sa vie avec des threads) façon de générer un processus identique à celui que vous exécutez actuellement (ce qui est ce que fork
fait sur unix)...on pourrait, sauf qu'il n'y en a pas: ça ne fait pas partie du modèle all-threads-all-the-time. (Ce qui n'est pas une mauvaise chose, mais c'est différent).
Vous fork
chaque fois que vous voulez plus qu'une chose en même temps. Ça s'appelle le multitâche, et c'est vraiment utile.
Voici par exemple un programme telnetish comme:
#!/usr/bin/perl
use strict;
use IO::Socket;
my ($host, $port, $kidpid, $handle, $line);
unless (@ARGV == 2) { die "usage: "151900920" host port" }
($host, $port) = @ARGV;
# create a tcp connection to the specified host and port
$handle = IO::Socket::INET->new(Proto => "tcp",
PeerAddr => $host,
PeerPort => $port)
or die "can't connect to port $port on $host: $!";
$handle->autoflush(1); # so output gets there right away
print STDERR "[Connected to $host:$port]\n";
# split the program into two processes, identical twins
die "can't fork: $!" unless defined($kidpid = fork());
if ($kidpid) {
# parent copies the socket to standard output
while (defined ($line = <$handle>)) {
print STDOUT $line;
}
kill("TERM" => $kidpid); # send SIGTERM to child
}
else {
# child copies standard input to the socket
while (defined ($line = <STDIN>)) {
print $handle $line;
}
}
exit;
tu vois comme c'est facile?
Fork () est de cloner un serveur pour chaque nouveau client qui se connecte()s (parce que le nouveau processus hérite de tous les descripteurs de fichiers dans quelque état qu'ils existent). Mais je l'ai également utilisé pour lancer un nouveau service (local running) à la demande d'un client. Ce schéma est le mieux fait avec deux appels à fork() - l'un reste dans la session parent jusqu'à ce que le serveur soit opérationnel et capable de se connecter, l'autre (je le bifurque de l'enfant) devient le serveur et quitte le la session du parent de sorte qu'il ne peut plus être atteint par (disons) SIGQUIT.