Programación en C

Su primer programa en C usando Fork System Call

Su primer programa en C usando Fork System Call
De forma predeterminada, los programas C no tienen simultaneidad ni paralelismo, solo se realiza una tarea a la vez, cada línea de código se lee secuencialmente. Pero a veces, tienes que leer un archivo o - aún peor - un enchufe conectado a una computadora remota y esto lleva mucho tiempo para una computadora. Por lo general, toma menos de un segundo, pero recuerde que un solo núcleo de CPU puede ejecutar 1 o 2 mil millones de instrucciones durante ese tiempo.

Entonces, como buen desarrollador, tendrá la tentación de instruir a su programa C para que haga algo más útil mientras espera. Ahí es donde la programación de concurrencia está aquí para su rescate - y hace que tu computadora se sienta infeliz porque tiene que funcionar más.

Aquí, le mostraré la llamada al sistema de la bifurcación de Linux, una de las formas más seguras de hacer programación concurrente.

La programación simultánea puede ser insegura?

Sí puede. Por ejemplo, también hay otra forma de llamar multihilo. Tiene la ventaja de ser más ligero pero puede De Verdad ir mal si lo usas incorrectamente. Si su programa, por error, lee una variable y escribe en el misma variable al mismo tiempo, su programa se volverá incoherente y será casi indetectable - una de las peores pesadillas de los desarrolladores.

Como verá a continuación, la bifurcación copia la memoria, por lo que no es posible tener tales problemas con las variables. Además, fork hace un proceso independiente para cada tarea concurrente. Debido a estas medidas de seguridad, es aproximadamente 5 veces más lento lanzar una nueva tarea concurrente usando una bifurcación que con subprocesos múltiples. Como puede ver, eso no es mucho por los beneficios que aporta.

Ahora, basta de explicaciones, es hora de probar su primer programa en C usando la llamada a la bifurcación.

El ejemplo de la bifurcación de Linux

Aquí está el código:

#incluir
#incluir
#incluir
#incluir
#incluir
int main ()
pid_t forkStatus;
forkStatus = fork ();
/* Niño… */
if (forkStatus == 0)
printf ("El niño se está ejecutando, procesando.\norte");
dormir (5);
printf ("El niño ha terminado, saliendo.\norte");
/ * Padre… * /
else if (forkStatus != -1)
printf ("Padre está esperando ... \ n");
esperar (NULO);
printf ("El padre está saliendo ... \ n");
demás
perror ("Error al llamar a la función fork");

return 0;

Te invito a probar, compilar y ejecutar el código anterior, pero si quieres ver cómo se vería el resultado y eres demasiado "vago" para compilarlo - después de todo, tal vez sea un desarrollador cansado que compiló programas en C todo el día - puede encontrar la salida del programa C a continuación junto con el comando que usé para compilarlo:

$ gcc -std = c89 -Wpedantic -Wall forkSleep.c -o forkSleep -O2
PS ./ forkSleep
Padre está esperando ..
El niño está corriendo, procesando.
El niño ha terminado, saliendo.
Padre está saliendo ..

No tenga miedo si la salida no es 100% idéntica a mi salida anterior. Recuerde que ejecutar cosas al mismo tiempo significa que las tareas se están ejecutando fuera de orden, no hay un orden predefinido. En este ejemplo, es posible que vea que el niño está corriendo antes de padre está esperando, y no hay nada de malo en eso. En general, el orden depende de la versión del kernel, la cantidad de núcleos de CPU, los programas que se están ejecutando actualmente en su computadora, etc.

OK, ahora vuelve al código. Antes de la línea con fork (), este programa en C es perfectamente normal: se está ejecutando 1 línea a la vez, solo hay un proceso para este programa (si hubo un pequeño retraso antes de la bifurcación, puede confirmarlo en su administrador de tareas).

Después de la bifurcación (), ahora hay 2 procesos que pueden ejecutarse en paralelo. Primero, hay un proceso secundario. Este proceso es el que se ha creado en fork (). Este proceso hijo es especial: no ha ejecutado ninguna de las líneas de código por encima de la línea con fork (). En lugar de buscar la función principal, prefiere ejecutar la línea fork ().

¿Qué pasa con las variables declaradas antes de la bifurcación??

Bueno, Linux fork () es interesante porque responde inteligentemente a esta pregunta. Variables y, de hecho, toda la memoria en los programas C se copia en el proceso hijo.

Permítanme definir lo que está haciendo fork en pocas palabras: crea un clon del proceso llamándolo. Los 2 procesos son casi idénticos: todas las variables contendrán los mismos valores y ambos procesos ejecutarán la línea justo después de fork (). Sin embargo, después del proceso de clonación, ellos estan separados. Si actualiza una variable en un proceso, el otro proceso no lo haré tener su variable actualizada. Es realmente un clon, una copia, los procesos no comparten casi nada. Es realmente útil: puede preparar una gran cantidad de datos y luego bifurcar () y usar esos datos en todos los clones.

La separación comienza cuando fork () devuelve un valor. El proceso original (se llama el proceso padre) obtendrá el ID de proceso del proceso clonado. Por otro lado, el proceso clonado (este se llama el proceso del niño) obtendrá el número 0. Ahora, debería comenzar a comprender por qué he puesto las declaraciones if / else if después de la línea fork (). Usando el valor de retorno, puede indicarle al niño que haga algo diferente de lo que está haciendo el padre - y créeme, es útil.

Por un lado, en el código de ejemplo anterior, el niño está haciendo una tarea que toma 5 segundos e imprime un mensaje. Para imitar un proceso que lleva mucho tiempo utilizo la función dormir. Entonces, el niño sale con éxito.

En el otro lado, el padre imprime un mensaje, espera hasta que el niño salga y finalmente imprime otro mensaje. El hecho de que los padres esperen a su hijo es importante. Como ejemplo, el padre está pendiente la mayor parte de este tiempo para esperar a su hijo. Pero, podría haberle dado instrucciones al padre para que hiciera cualquier tipo de tarea de larga duración antes de decirle que esperara. De esta forma, habría realizado tareas útiles en lugar de esperar - después de todo, es por eso que usamos tenedor (), no?

Sin embargo, como dije anteriormente, es muy importante que el padre espera a sus hijos. Y es importante por procesos zombies.

Lo importante que es esperar

Los padres generalmente quieren saber si los niños han terminado su procesamiento. Por ejemplo, desea ejecutar tareas en paralelo pero ciertamente no quieres el padre salga antes de que los niños terminen, porque si sucediera, el shell devolvería un mensaje mientras los niños aún no hayan terminado - que es raro.

La función de espera permite esperar hasta que termine uno de los procesos secundarios. Si un padre llama 10 veces a la bifurcación (), también deberá llamar 10 veces a la espera (), una vez para cada niño creado.

Pero, ¿qué sucede si los padres llaman a la función de espera mientras todos los niños tienen ya salido? Ahí es donde se necesitan los procesos zombies.

Cuando un niño sale antes de que los padres llamen a wait (), el kernel de Linux permitirá que el niño salga pero se quedará con un boleto decirle al niño que ha salido. Luego, cuando el padre llame a wait (), encontrará el ticket, eliminará ese ticket y la función wait () regresará inmediatamente porque sabe que el padre necesita saber cuando el niño ha terminado. Este boleto se llama proceso zombie.

Por eso es importante que los padres llamen a wait (): si no lo hace, los procesos zombie permanecen en la memoria y en el kernel de Linux hipocresía mantener muchos procesos zombies en la memoria. Una vez que se alcanza el límite, su computadoras incapaz de crear ningún proceso nuevo y así estarás en un muy mal estado: incluso para matar un proceso, es posible que deba crear un nuevo proceso para ese. Por ejemplo, si desea abrir su administrador de tareas para matar un proceso, no puede, porque su administrador de tareas necesitará un nuevo proceso. Aún peor, No puedes matar un proceso zombie.

Es por eso que llamar a wait es importante: permite que el kernel limpiar el proceso hijo en lugar de seguir acumulando una lista de procesos terminados. ¿Y si el padre se va sin llamar nunca? Espere()?

Afortunadamente, como el padre termina, nadie más puede llamar a wait () para estos hijos, por lo que hay Sin razón para mantener estos procesos zombies. Por lo tanto, cuando un padre sale, todo restante procesos zombies vinculado a este padre son removidos. Los procesos zombies son De Verdad solo es útil para permitir que los procesos padre descubran que un hijo terminó antes de que el padre llamara a esperar ().

Ahora, es posible que prefieras conocer algunas medidas de seguridad que te permitan el mejor uso de la horquilla sin ningún problema.

Reglas simples para que la bifurcación funcione según lo previsto

Primero, si conoce el subproceso múltiple, no bifurque un programa que utilice subprocesos. De hecho, evite en general mezclar múltiples tecnologías de concurrencia. fork asume que funciona en programas C normales, solo tiene la intención de clonar una tarea paralela, no más.

En segundo lugar, evite abrir o abrir archivos antes de fork (). Los archivos son una de las únicas cosas compartido y no clonado entre padre e hijo. Si lee 16 bytes en el padre, moverá el cursor de lectura hacia adelante de 16 bytes ambas cosas en el padre y en el niño. Peor, si el niño y el padre escriben bytes en el mismo archivo al mismo tiempo, los bytes del padre pueden ser mezclado con bytes del niño!

Para ser claros, fuera de STDIN, STDOUT y STDERR, realmente no desea compartir ningún archivo abierto con clones.

En tercer lugar, tenga cuidado con los enchufes. Los enchufes son también compartido entre padres e hijos. Es útil para escuchar un puerto y luego dejar que varios trabajadores secundarios estén listos para manejar una nueva conexión de cliente. sin emabargo, si lo usas incorrectamente, te meterás en problemas.

Cuarto, si desea llamar a fork () dentro de un bucle, haga esto con extremo cuidado. Tomemos este código:

/ * NO COMPILES ESTO * /
const int targetFork = 4;
pid_t forkResult
 
para (int i = 0; i < targetFork; i++)
forkResult = fork ();
/ *… * /
 

Si lee el código, puede esperar que cree 4 hijos. Pero preferirá crear 16 niños. Es porque los niños lo harán además ejecutar el ciclo y así los niños, a su vez, llamarán a fork (). Cuando el bucle es infinito, se llama bomba de horquilla y es una de las formas de ralentizar un sistema Linux tanto que ya no funciona y necesitará un reinicio. En pocas palabras, tenga en cuenta que Clone Wars no solo es peligroso en Star Wars!

Ahora que ha visto cómo un bucle simple puede salir mal, cómo usar bucles con fork ()? Si necesita un bucle, siempre verifique el valor de retorno de la bifurcación:

const int targetFork = 4;
pid_t forkResult;
int i = 0;
hacer
forkResult = fork ();
/ *… * /
i ++;
while ((forkResult != 0 && forkResult != -1) && (i < targetFork));

Conclusión

Ahora es el momento de que hagas tus propios experimentos con fork ()! Pruebe formas novedosas de optimizar el tiempo realizando tareas en varios núcleos de CPU o realice un procesamiento en segundo plano mientras espera leer un archivo!

No dude en leer las páginas del manual mediante el comando man. Aprenderá cómo funciona con precisión fork (), qué errores puede obtener, etc. Y disfruta de la concurrencia!

Vuelva a asignar los botones del mouse de manera diferente para diferentes programas con X-Mouse Button Control
Tal vez necesite una herramienta que pueda hacer que el control de su mouse cambie con cada aplicación que use. Si este es el caso, puede probar una a...
Revisión del mouse inalámbrico Microsoft Sculpt Touch
Recientemente leí sobre el Microsoft Sculpt Touch mouse inalámbrico y decidí comprarlo. Después de usarlo por un tiempo, decidí compartir mi experienc...
Trackpad en pantalla y puntero del mouse AppyMouse para tabletas Windows
Los usuarios de tabletas a menudo pierden el puntero del mouse, especialmente cuando son habituales para usar las computadoras portátiles. Los teléfon...