what syscall pid_t does c++ c multithreading unix fork

c++ - syscall - wait c function



Comparación justa de la bifurcación() Vs Hilo (3)

Bajo Linux, fork es una llamada especial a sys_clone , ya sea dentro de la biblioteca o dentro del kernel. Clone tiene muchos interruptores para encender y apagar, y cada uno de ellos afecta lo costoso que es comenzar.

El clone real de la función de biblioteca es probablemente más costoso que el fork porque hace más, aunque la mayor parte está en el lado secundario (intercambio de pila y llamada a una función mediante un puntero).

Estaba teniendo una discusión sobre el costo relativo de fork () Vs thread () para paralelizar una tarea.

Entendemos las diferencias básicas entre procesos Vs Thread.

Hilo:

  • Fácil de comunicar entre hilos.
  • Cambio rápido de contexto.

Procesos:

  • Tolerancia a fallos.
  • La comunicación con los padres no es un problema real (abrir una tubería)
  • Comunicación con otros procesos infantiles difíciles.

Pero no estábamos de acuerdo con el costo de inicio de los procesos frente a los subprocesos.
Así que para probar las teorías escribí el siguiente código. Mi pregunta: ¿Es esta una prueba válida para medir el costo inicial o me estoy perdiendo algo? También me interesaría saber cómo se realiza cada prueba en diferentes plataformas.

fork.cpp

#include <boost/lexical_cast.hpp> #include <vector> #include <unistd.h> #include <iostream> #include <stdlib.h> #include <time.h> extern "C" int threadStart(void* threadData) { return 0; } int main(int argc,char* argv[]) { int threadCount = boost::lexical_cast<int>(argv[1]); std::vector<pid_t> data(threadCount); clock_t start = clock(); for(int loop=0;loop < threadCount;++loop) { data[loop] = fork(); if (data[looo] == -1) { std::cout << "Abort/n"; exit(1); } if (data[loop] == 0) { exit(threadStart(NULL)); } } clock_t middle = clock(); for(int loop=0;loop < threadCount;++loop) { int result; waitpid(data[loop], &result, 0); } clock_t end = clock(); std::cout << threadCount << "/t" << middle - start << "/t" << end - middle << "/t"<< end - start << "/n"; }

Thread.cpp

#include <boost/lexical_cast.hpp> #include <vector> #include <iostream> #include <pthread.h> #include <time.h> extern "C" void* threadStart(void* threadData) { return NULL; } int main(int argc,char* argv[]) { int threadCount = boost::lexical_cast<int>(argv[1]); std::vector<pthread_t> data(threadCount); clock_t start = clock(); for(int loop=0;loop < threadCount;++loop) { if (pthread_create(&data[loop], NULL, threadStart, NULL) != 0) { std::cout << "Abort/n"; exit(1); } } clock_t middle = clock(); for(int loop=0;loop < threadCount;++loop) { void* result; pthread_join(data[loop], &result); } clock_t end = clock(); std::cout << threadCount << "/t" << middle - start << "/t" << end - middle << "/t"<< end - start << "/n"; }

Espero que a Windows le vaya peor en la creación de procesos.
Pero yo esperaría que los sistemas modernos similares a Unix tuvieran un costo de bifurcación bastante ligero y al menos fueran comparables a los hilos. En los sistemas de estilo Unix más antiguos (antes de que fork () se implementara utilizando copias en las páginas de escritura) sería peor.

De todos modos mis resultados de tiempo son:

> uname -a Darwin Alpha.local 10.4.0 Darwin Kernel Version 10.4.0: Fri Apr 23 18:28:53 PDT 2010; root:xnu-1504.7.4~1/RELEASE_I386 i386 > gcc --version | grep GCC i686-apple-darwin10-gcc-4.2.1 (GCC) 4.2.1 (Apple Inc. build 5659) > g++ thread.cpp -o thread -I~/include > g++ fork.cpp -o fork -I~/include > foreach a ( 1 2 3 4 5 6 7 8 9 10 12 15 20 30 40 50 60 70 80 90 100 ) foreach? ./thread ${a} >> A foreach? end > foreach a ( 1 2 3 4 5 6 7 8 9 10 12 15 20 30 40 50 60 70 80 90 100 ) foreach? ./fork ${a} >> A foreach? end vi A Thread: Fork: C Start Wait Total C Start Wait Total ============================================================== 1 26 145 171 1 160 37 197 2 44 198 242 2 290 37 327 3 62 234 296 3 413 41 454 4 77 275 352 4 499 59 558 5 91 107 10808 5 599 57 656 6 99 332 431 6 665 52 717 7 130 388 518 7 741 69 810 8 204 468 672 8 833 56 889 9 164 469 633 9 1067 76 1143 10 165 450 615 10 1147 64 1211 12 343 585 928 12 1213 71 1284 15 232 647 879 15 1360 203 1563 20 319 921 1240 20 2161 96 2257 30 461 1243 1704 30 3005 129 3134 40 559 1487 2046 40 4466 166 4632 50 686 1912 2598 50 4591 292 4883 60 827 2208 3035 60 5234 317 5551 70 973 2885 3858 70 7003 416 7419 80 3545 2738 6283 80 7735 293 8028 90 1392 3497 4889 90 7869 463 8332 100 3917 4180 8097 100 8974 436 9410

Editar:

Hacer 1000 niños hizo que la versión de la horquilla fallara.
Así que he reducido la cuenta de los niños. Pero hacer una sola prueba también parece injusto, así que aquí hay un rango de valores.


Lo que muestra ese micro-punto de referencia es que la creación y unión de subprocesos (no hay resultados de bifurcación cuando escribo esto) toma decenas o cientos de microsegundos (suponiendo que su sistema tenga CLOCKS_PER_SEC = 1000000, lo que probablemente haya hecho, ya que es un requisito XSI ).

Como usted dijo que fork () toma 3 veces el costo de los subprocesos, en el peor de los casos todavía estamos hablando de décimas de milisegundo. Si eso se nota en una aplicación, podría usar grupos de procesos / subprocesos, como hizo Apache 1.3. En cualquier caso, diría que el tiempo de inicio es un punto discutible.

La diferencia importante de los subprocesos frente a los procesos (en Linux y en la mayoría de los Me gusta de Unix) es que en los procesos usted elige explícitamente qué compartir, usando IPC, memoria compartida (estilo SYSV o mmap), tuberías, sockets (puede enviar descriptores de archivos a través de AF_UNIX sockets, lo que significa que puede elegir qué fd''s compartir, ... Mientras que en los subprocesos, casi todo se comparte de forma predeterminada, ya sea que sea necesario compartirlo o no. De hecho, esa es la razón por la que Plan 9 tenía rfork () y Linux tiene clone () (y recientemente unshare ()), para que pueda elegir qué compartir.


Mumble ... No me gusta tu solución por muchas razones:

  1. No está teniendo en cuenta el tiempo de ejecución de procesos secundarios / subproceso.

  2. Debe comparar el uso de la CPU, no el tiempo transcurrido. De esta manera, sus estadísticas no dependerán de, por ejemplo, la congestión de acceso al disco.

  3. Deja que tu hijo procese hacer algo. Recuerde que la bifurcación "moderna" utiliza mecanismos de copia en escritura para evitar asignar memoria al proceso hijo hasta que sea necesario. Es demasiado fácil salir de inmediato. De esta manera evitas todas las desventajas de tenedor.

  4. El tiempo de CPU no es el único costo que hay que tener en cuenta. El consumo de memoria y la lentitud de IPC son desventajas de la solución de horquilla.

Podría usar "rusage" en lugar de "reloj" para medir el uso de recursos reales.

PD: no creo que realmente pueda medir el proceso o la sobrecarga de subprocesos escribiendo un programa de prueba simple. Hay demasiados factores y, por lo general, la elección entre subprocesos y procesos se debe a otras razones que no son el simple uso de la CPU.