Concurrencia: ¿Cómo aborda el diseño y depura la implementación?


37

He estado desarrollando sistemas concurrentes durante varios años y tengo una buena comprensión del tema a pesar de mi falta de capacitación formal (es decir, sin título). Hay algunos nuevos idiomas que se han vuelto populares para hablar al menos últimamente y que están diseñados para facilitar la concurrencia, como Erlang y Go. Parece que su enfoque de concurrencia se hace eco de mi propia experiencia sobre cómo hacer que los sistemas sean escalables y aprovechar múltiples núcleos / procesadores / máquinas.

Sin embargo, creo que hay muy pocas herramientas para ayudar a visualizar lo que tiene la intención de hacer y verificar que al menos está cerca de su visión original. La depuración de código concurrente puede ser una pesadilla con lenguajes que no están diseñados para la concurrencia (como C / C ++, C #, Java, etc.). En particular, puede ser casi imposible recrear condiciones que suceden fácilmente en un sistema en su entorno de desarrollo.

Entonces, ¿cuáles son sus enfoques para diseñar un sistema para lidiar con la concurrencia y el procesamiento paralelo? Ejemplos:

  • ¿Cómo se da cuenta de lo que puede hacerse concurrente frente a lo que tiene que ser secuencial?
  • ¿Cómo reproduce las condiciones de error y ve lo que sucede mientras se ejecuta la aplicación?
  • ¿Cómo visualiza las interacciones entre las diferentes partes concurrentes de la aplicación?

Tengo mis propias respuestas para algunos de estos, pero también me gustaría aprender un poco más.

Editar

Hasta ahora tenemos muchos buenos comentarios. Muchos de los artículos vinculados son muy buenos, y ya he leído algunos de ellos.

Mi experiencia personal con la programación concurrente me lleva a creer que necesita una mentalidad diferente a la que necesita con la programación secuencial. La brecha mental es probablemente tan amplia como la diferencia entre la programación orientada a objetos y la programación de procedimientos. Me gustaría que este conjunto de preguntas se centre más en los procesos de pensamiento necesarios (es decir, la teoría) para abordar sistemáticamente las respuestas. Al proporcionar respuestas más concretas, es útil dar un ejemplo, algo por lo que pasó personalmente.

Gol para la recompensa

No me digas que debo hacer. Ya lo tengo bajo control. Dime lo que haces. Dime cómo se soluciona estos problemas.


Esta es una buena pregunta: mucha profundidad posible. También obtuve una buena experiencia con aplicaciones multiproceso en Java, pero me gustaría obtener más información.
Michael K

Hasta ahora, tenemos algunas buenas respuestas. ¿Alguien quiere arriesgarse con lo que desearía tener para ayudarlo en esta área?
Berin Loritsch

TotalView Debugger para codificación concurrente es una herramienta bastante útil, aunque requiere un poco de curva de aprendizaje - totalviewtech.com/products/totalview.html
Fanatic23

Tal vez el registro podría ayudarlo con dos últimas preguntas.
Amir Rezaei

Lo que estoy buscando son los procesos de las personas. Estas son áreas donde las herramientas que he estado usando son inadecuadas, pero pueden hacer el trabajo. Estoy menos preocupado por citar el artículo de otra persona y más preocupado por la metodología aquí.
Berin Loritsch

Respuestas:


11

He estado desarrollando sistemas concurrentes durante varios años y tengo una buena comprensión del tema a pesar de mi falta de capacitación formal (es decir, sin título).

Muchos de los mejores programadores que conozco no terminaron la universidad. En cuanto a mí, estudié filosofía.

C / C ++, C #, Java, etc.). En particular, puede ser casi imposible recrear condiciones que suceden fácilmente en un sistema en su entorno de desarrollo.

¿Cómo se da cuenta de lo que puede hacerse concurrente frente a lo que tiene que ser secuencial?

Por lo general, comenzamos con una metáfora de 1000 millas de altura para aclarar nuestra arquitectura a nosotros mismos (en primer lugar) y a los demás (en segundo lugar).

Cuando nos enfrentamos a ese problema, siempre encontramos una manera de limitar la visibilidad de los objetos concurrentes a los no concurrentes.

Últimamente descubrí Actores en scala y vi que mis viejas soluciones eran una especie de "miniactores", mucho menos poderosos que los scala. Entonces mi sugerencia es comenzar desde allí.

Otra sugerencia es omitir tantos problemas como sea posible: por ejemplo, usamos caché centralizada (terracota) en lugar de mantener mapas en la memoria, usar devoluciones de llamada de clase interna en lugar de métodos sincronizados, enviar mensajes en lugar de escribir memoria compartida, etc.

Con scala todo es mucho más fácil de todos modos.

¿Cómo reproduce las condiciones de error y ve lo que sucede mientras se ejecuta la aplicación?

No hay una respuesta real aquí. Tenemos algunas pruebas unitarias de concurrencia y tenemos un conjunto de pruebas de carga para estresar la aplicación tanto como podamos.

¿Cómo visualiza las interacciones entre las diferentes partes concurrentes de la aplicación?

Una vez más, no hay una respuesta real: diseñamos nuestra metáfora en la pizarra e intentamos asegurarnos de que no haya conflictos en el lado arquitectónico.

Para Arch aquí me refiero a la definición de Neal Ford: Sw Architecture es todo lo que será muy difícil de cambiar más adelante.

la programación me lleva a creer que necesitas una mentalidad diferente a la que tienes con la programación secuencial.

Tal vez, pero para mí es simplemente imposible pensar de manera paralela, por lo que es mejor diseñar nuestro software de una manera que no requiera un pensamiento paralelo y con barandillas claras para evitar choques entre carriles de concurrencia.


6

Para mí todo se trata de los datos. Divide tus datos correctamente, y el procesamiento paralelo es fácil. Todos los problemas de retención, puntos muertos, y así desaparecen.

Sé que esta no es la única forma de paralelizar, pero para mí es la más útil.

Para ilustrar, una historia (no tan rápida):

Trabajé en un gran sistema financiero (control del mercado de valores) entre 2007 y 2009, y el volumen de procesamiento de los datos fue muy grande. Para ilustrar, todos los cálculos realizados en una sola cuenta de un cliente tomaron aproximadamente 1 ~ 3 segundos en su estación de trabajo promedio, y hubo más de 30k cuentas. Todas las noches, cerrar el sistema fue un gran dolor para los usuarios (generalmente más de 6 horas de procesamiento, sin ningún margen de error para ellos).

Estudiar el problema reveló además que podríamos paralelizar los cálculos entre varias computadoras, pero aún tendríamos un enorme cuello de botella en el antiguo servidor de la base de datos (un servidor SQL 2000 que emula SQL 6.5).

Estaba bastante claro que nuestro paquete de procesamiento mínimo era el cálculo de una cuenta única, y el principal cuello de botella era la retención del servidor de la base de datos (pudimos ver en el "sp_who" varias conexiones esperando para hacer el mismo procesamiento). Entonces el proceso paralelo fue así:

1) Un solo productor, responsable de leer la base de datos o escribir en ella, secuencialmente. No se permite concurrencia aquí. El productor preparó una cola de trabajos, para los consumidores. La base de datos pertenecía únicamente a este productor.

2) Varios consumidores, en varias máquinas. Cada uno de los consumidores recibió un paquete completo de datos, de la cola, listo para calcular. Cada operación deqeue está sincronizada.

3) Después del cálculo, cada consumidor devolvió los datos a una cola sincronizada en memoria para el productor, a fin de conservar los datos.

Hubo varios puntos de control, varios mecanismos para asegurar que las transacciones se guardaron correctamente (no se dejó ninguno), pero todo el trabajo valió la pena. Al final, los cálculos distribuidos entre 10 computadoras (más la computadora productora / cola) redujeron el tiempo de cierre del sistema completo a 15 minutos.

Simplemente eliminando los problemas de retención causados ​​por la mala gestión de concurrencia, SQL 6.5 nos dio una gran ventaja. El resto fue bastante lineal, cada nueva computadora agregada a la "cuadrícula" redujo el tiempo de procesamiento, hasta que alcanzamos la "máxima eficiencia" de las operaciones secuenciales de lectura / escritura en la base de datos.


2

Trabajar en un entorno de subprocesos múltiples es difícil y necesita la disciplina de codificación. Debe seguir las pautas adecuadas para tomar el bloqueo, liberar el bloqueo, acceder a variables globales, etc.

Déjame intentar responder a tu pregunta, adiós

* How do you figure out what can be made concurrent vs. what has to be sequential?

Usar concurrencia para

1) Sondeo: - necesita un hilo para sondear continuamente algo o enviar la actualización de forma regular. (Conceptos como bits de corazón, que envían algunos datos en intervalos regulares al servidor central para decir que estoy vivo).

2) Las operaciones que tienen E / S pesadas podrían hacerse paralelas. El mejor ejemplo es el registrador. El hilo del registrador podría ser un hilo separado.

3) Tareas similares en diferentes datos. Si hay alguna tarea que ocurre en datos diferentes pero de naturaleza muy similar, diferentes hilos pueden hacer esto. El mejor ejemplo serán las solicitudes del servidor.

Y, por supuesto, muchos otros como este dependiendo de la aplicación.

* How do you reproduce error conditions and view what is happening as the application executes?

Uso de registros e impresiones de depuración en los registros. Intente registrar también la identificación del hilo para que pueda ver lo que está sucediendo en cada hilo.
Una forma de producir una condición de error es colocar el retraso deliberado (en el código de depuración) en los lugares donde cree que está ocurriendo el problema y detener ese hilo de manera forzada. También se pueden hacer cosas similares en depuradores, pero no lo he hecho hasta ahora.

* How do you visualize the interactions between the different concurrent parts of the application?

Coloque los registros en sus cerraduras, para que sepa quién está bloqueando qué y cuándo, y quién ha intentado bloquear. Como dije anteriormente, intente poner la identificación del hilo en el registro para comprender lo que está sucediendo en cada hilo.

Este es solo mi consejo, que es de alrededor de 3 años de trabajo en aplicaciones multiproceso, y espero que ayude.


2
  • ¿Cómo se da cuenta de lo que puede hacerse concurrente frente a lo que tiene que ser secuencial?

Primero me preguntaría si la aplicación (o componente) realmente verá un beneficio del procesamiento concurrente, o en términos simples: ¿dónde está el cuello de botella? La simultaneidad obviamente no siempre proporcionará un beneficio para la inversión que se necesita para que funcione. Si parece un candidato, entonces trabajaría de abajo hacia arriba, tratando de encontrar la operación más grande o el conjunto de operaciones que pueden hacer su trabajo de manera efectiva de forma aislada, no quiero hilar hilos por insignificantes e ineficaces. operaciones - Estoy buscando actores .

Al trabajar con Erlang, he llegado a amar absolutamente el concepto de usar el paso de mensajes asíncrono y el modelo de actor para la concurrencia: es intuitivo, efectivo y limpio.

Concurrencia del actor fuera de entendimiento

El modelo de actor consta de algunos principios clave:

  • Sin estado compartido
  • Procesos ligeros
  • Mensaje asincrónico que pasa
  • Buzones para almacenar temporalmente los mensajes entrantes
  • Procesamiento de buzones con coincidencia de patrones

Un actor es un proceso que ejecuta una función. Aquí, un proceso es un subproceso de espacio de usuario ligero (que no debe confundirse con un proceso típico de sistema operativo pesado). Los actores nunca comparten el estado y, por lo tanto, nunca necesitan competir por bloqueos para acceder a datos compartidos. En cambio, los actores comparten datos enviando mensajes que son inmutables. Los datos inmutables no se pueden modificar, por lo que las lecturas no requieren un bloqueo.

El modelo de concurrencia de Erlang es más fácil de entender y depurar que bloquear y compartir datos. La forma en que se aísla su lógica hace que sea fácil hacer pruebas de componentes al pasarles mensajes.

Trabajando con sistemas concurrentes, así es más o menos como funcionaba mi diseño en cualquier idioma: una cola de la que varios hilos extraerían datos, realizarían una operación simple y repetirían o volverían a la cola. Erlang solo impone estructuras de datos inmutables para evitar efectos secundarios y reducir el costo y la complejidad de crear nuevos hilos.

Este modelo no es exclusivo de Erlang, incluso en el mundo de Java y .NET existen formas de crear esto: vería el tiempo de ejecución de concurrencia y coordinación (CCR) y Relang (también existe Jetlang para Java).

  • ¿Cómo reproduce las condiciones de error y ve lo que sucede mientras se ejecuta la aplicación?

En mi experiencia, lo único que puedo hacer es comprometerme a rastrear / registrar todo. Cada proceso / subproceso debe tener un identificador y cada nueva unidad de trabajo debe tener una identificación de correlación. Debes poder mirar a través de tus registros y rastrear exactamente qué se estaba procesando y cuándo, no hay magia que haya visto para eliminar esto.

  • ¿Cómo visualiza las interacciones entre las diferentes partes concurrentes de la aplicación?

Ver arriba, es feo pero funciona. La única otra cosa que hago es usar diagramas de secuencia UML, por supuesto, esto es durante el tiempo de diseño, pero puede usarlos para verificar que sus componentes estén hablando de la manera que usted también los quiere.


1

- Mis respuestas son específicas de MS / Visual Studio -

¿Cómo se da cuenta de lo que puede hacerse concurrente frente a lo que tiene que ser secuencial?

Eso requerirá conocimiento del dominio, no habrá ninguna declaración general aquí para cubrirlo.

¿Cómo reproduce las condiciones de error y ve lo que sucede mientras se ejecuta la aplicación?

Gran cantidad de registros, pudiendo activar / desactivar / activar el registro en aplicaciones de producción para capturarlo en producción. Se supone que VS2010 Intellitrace puede ayudar con esto, pero aún no lo he usado.

¿Cómo visualiza las interacciones entre las diferentes partes concurrentes de la aplicación?

No tengo una buena respuesta a esto, me encantaría ver una.


El registro cambiará la forma en que se ejecuta el código y, por lo tanto, puede provocar el error que está después de no aparecer.
Mateo leyó el

1

No estoy de acuerdo con su afirmación de que C no está diseñado para la concurrencia. C está diseñado para la programación general de sistemas y disfruta de una tenacidad para señalar las decisiones críticas que se tomarán, y continuará haciéndolo en los próximos años. Esto es cierto incluso cuando la mejor decisión podría ser no usar C. Además, la concurrencia en C es tan difícil como su diseño es complejo.

Intento, lo mejor que puedo, implementar bloqueos con la idea de que, eventualmente, sea realmente práctico sin bloqueo podría convertirse en una realidad para mí. Al bloquear no me refiero a la exclusión mutua, simplemente me refiero a un proceso que implementa concurrencia segura sin la necesidad de arbitraje. Por práctico, me refiero a algo que es más fácil de portar que implementar. También tengo muy poca capacitación formal en CS, pero supongo que se me permite desear :)

Después de eso, la mayoría de los errores que encuentro se vuelven relativamente poco profundos, o tan completamente alucinantes que me retiro a un pub. El pub se convierte en una opción atractiva solo cuando perfilar un programa lo ralentiza lo suficiente como para exponer carreras adicionales que no están relacionadas con lo que estoy tratando de encontrar.

Como otros han señalado, el problema que usted describe es extremadamente específico del dominio. Solo intento, con lo mejor de mi capacidad, evitar cualquier caso que pueda requerir arbitraje (fuera de mi proceso) siempre que sea posible. Si parece que podría ser un dolor real, reevalúo la opción de dar múltiples hilos o procesos de acceso concurrente y no serializado a algo.

Por otra parte, tirar 'distribuido' allí y el arbitraje se convierte en un deber. ¿Tienes un ejemplo específico?


Para aclarar mi afirmación, C no fue diseñado específicamente para y alrededor de la concurrencia. Esto está en contraste con lenguajes como Go, Erlang y Scala que fueron diseñados explícitamente con la concurrencia en mente. No tenía la intención de decir que no puedes hacer concurrencia con C.
Berin Loritsch

1

¿Cómo reproduce las condiciones de error y ve lo que sucede mientras se ejecuta la aplicación?

¿Cómo visualiza las interacciones entre las diferentes partes concurrentes de la aplicación?

Según mi experiencia, la respuesta a estos dos aspectos es la siguiente:

Rastreo distribuido

El rastreo distribuido es una tecnología que captura datos de tiempo para cada componente concurrente individual de su sistema y se los presenta en formato gráfico. Las representaciones de ejecuciones concurrentes siempre se entrelazan, lo que le permite ver qué se ejecuta en paralelo y qué no.

El rastreo distribuido debe sus orígenes en (por supuesto) los sistemas distribuidos, que por definición son asíncronos y altamente concurrentes. Un sistema distribuido con rastreo distribuido permite a las personas:

a) identifique cuellos de botella importantes, b) obtenga una representación visual de las 'ejecuciones' ideales de su aplicación, yc) proporcione visibilidad sobre el comportamiento concurrente que se está ejecutando, d) obtenga datos de tiempo que pueden usarse para evaluar las diferencias entre los cambios en su sistema (extremadamente importante si tiene SLA fuertes).

Sin embargo, las consecuencias del rastreo distribuido son:

  1. Agrega sobrecarga a todos sus procesos concurrentes, ya que se traduce en más código para ejecutar y enviar potencialmente a través de una red. En algunos casos, esta sobrecarga es muy significativa, incluso Google solo usa su sistema de seguimiento Dapper en un pequeño subconjunto de todas las solicitudes para no arruinar la experiencia del usuario.

  2. Existen muchas herramientas diferentes, no todas las cuales son interoperables entre sí. Esto está algo mejorado por estándares como OpenTracing, pero no está completamente resuelto.

  3. No le dice nada sobre los recursos compartidos y su estado actual. Es posible que pueda adivinar, según el código de la aplicación y lo que le muestra el gráfico que ve, pero no es una herramienta útil a este respecto.

  4. Las herramientas actuales suponen que tiene memoria y almacenamiento de sobra. El alojamiento de un servidor de series de tiempo puede no ser barato, dependiendo de sus limitaciones.

Software de seguimiento de errores

Me conecto a Sentry arriba principalmente porque es la herramienta más utilizada y por una buena razón: software de seguimiento de errores como Sentry secuestra la ejecución en tiempo de ejecución para reenviar simultáneamente una traza de la pila de los errores encontrados a un servidor central.

El beneficio neto de dicho software dedicado en código concurrente:

  1. Los errores duplicados no se duplican . En otras palabras, si uno o más sistemas concurrentes encuentran la misma excepción, Sentry incrementará un informe de incidente, pero no enviará dos copias del incidente.

Esto significa que puede averiguar qué sistema concurrente está experimentando qué tipo de error sin tener que pasar por innumerables informes de error simultáneos. Si alguna vez ha sufrido correo no deseado desde un sistema distribuido, ya sabe cómo se siente el infierno.

Incluso puede 'etiquetar' diferentes aspectos de su sistema concurrente (aunque esto supone que no tiene trabajo entrelazado sobre exactamente un hilo, lo que técnicamente no es concurrente de todos modos ya que el hilo simplemente salta entre las tareas de manera eficiente pero aún debe procesar los controladores de eventos hasta completarlo) y ver un desglose de los errores por etiqueta.

  1. Puede modificar este software de manejo de errores para proporcionar detalles adicionales con sus excepciones de tiempo de ejecución. ¿Qué recursos abiertos tuvo el proceso? ¿Existe un recurso compartido que este proceso tenía? ¿Qué usuario experimentó este problema?

Esto, además de los rastros meticulosos de la pila (y los mapas de origen, si tiene que proporcionar una versión reducida de sus archivos), hace que sea fácil determinar qué va mal una gran parte del tiempo.

  1. (Específico de Sentry) Puede tener un panel de informes de Sentry separado para las ejecuciones de prueba del sistema, lo que le permite detectar errores en las pruebas.

Las desventajas de dicho software incluyen:

  1. Como todo, agregan a granel. Es posible que no desee dicho sistema en hardware integrado, por ejemplo. Recomiendo encarecidamente realizar una ejecución de prueba de dicho software, comparando una ejecución simple con y sin muestra de más de unos cientos de ejecuciones en una máquina inactiva.

  2. No todos los idiomas son igualmente compatibles, ya que muchos de estos sistemas dependen de la captura implícita de una excepción y no todos los idiomas presentan excepciones robustas. Dicho esto, hay clientes para una gran cantidad de sistemas.

  3. Pueden plantearse como un riesgo de seguridad, ya que muchos de estos sistemas son esencialmente de código cerrado. En tales casos, haga su debida diligencia al investigarlos o, si lo prefiere, haga lo suyo.

  4. Es posible que no siempre le brinden la información que necesita. Esto es un riesgo con todos los intentos de agregar visibilidad.

  5. La mayoría de estos servicios fueron diseñados para aplicaciones web altamente concurrentes, por lo que no todas las herramientas pueden ser perfectas para su caso de uso.

En resumen : tener visibilidad es la parte más crucial de cualquier sistema concurrente. Los dos métodos que describo anteriormente, junto con paneles de control dedicados sobre hardware y datos para obtener una imagen holística del sistema en cualquier punto de tiempo dado, son ampliamente utilizados en toda la industria precisamente para abordar ese aspecto.

Algunas sugerencias adicionales

He pasado más tiempo del que me importa para arreglar el código de personas que intentaron resolver problemas concurrentes de manera terrible. Cada vez, he encontrado casos en los que las siguientes cosas podrían mejorar en gran medida la experiencia del desarrollador (que es tan importante como la experiencia del usuario):

  • Confíe en los tipos . La escritura existe para validar su código, y se puede usar en tiempo de ejecución como protección adicional. Donde no exista la escritura, confíe en aserciones y un controlador de errores adecuado para detectar errores. El código concurrente requiere un código defensivo, y los tipos sirven como el mejor tipo de validación disponible.

    • Pruebe los enlaces entre los componentes del código , no solo el componente en sí. No confunda esto con una prueba de integración completa, que prueba cada enlace entre cada componente, e incluso entonces solo busca una validación global del estado final. Esta es una forma terrible de detectar errores.

Una buena prueba de enlace verifica si, cuando un componente habla con otro componente de forma aislada , el mensaje recibido y el mensaje enviado son los mismos que usted espera. Si tiene dos o más componentes que dependen de un servicio compartido para comunicarse, hágalos girar, haga que intercambien mensajes a través del servicio central y vea si todos obtienen lo que espera al final.

Romper las pruebas que involucran una gran cantidad de componentes en una prueba de los componentes mismos y una prueba de cómo cada uno de los componentes también se comunica le brinda una mayor confianza en la validez de su código. Tener un cuerpo de pruebas tan riguroso le permite hacer cumplir los contratos entre servicios, así como detectar errores inesperados que ocurren cuando se ejecutan a la vez.

  • Use los algoritmos correctos para validar el estado de su aplicación. Estoy hablando de cosas simples, como cuando tienes un proceso maestro esperando a que todos sus trabajadores terminen una tarea y solo quieres pasar al siguiente paso si todos los trabajadores están completamente hechos; este es un ejemplo de detección global terminación, para lo cual existen metodologías conocidas como el algoritmo de Safra.

Algunas de estas herramientas vienen con idiomas: Rust, por ejemplo, garantiza que su código no tendrá condiciones de carrera en tiempo de compilación, mientras que Go presenta un detector de punto muerto incorporado que también se ejecuta en tiempo de compilación. Si puede detectar problemas antes de que lleguen a la producción, siempre es una victoria.

Una regla general: diseño para fallas en sistemas concurrentes . Anticipe que los servicios comunes colapsarán o se romperán. Esto va incluso para el código que no se distribuye entre las máquinas: el código concurrente en una sola máquina puede depender de dependencias externas (como un archivo de registro compartido, un servidor Redis, un maldito servidor MySQL) que podrían desaparecer o eliminarse en cualquier momento .

La mejor manera de hacer esto es validar el estado de la aplicación de vez en cuando: tener controles de salud para cada servicio y asegurarse de que los consumidores de ese servicio sean notificados de un mal estado. Las herramientas de contenedores modernas como Docker lo hacen bastante bien, y deberían usarse para hacer sandbox.

¿Cómo calculas qué se puede hacer concurrente y qué se puede hacer secuencial?

Una de las lecciones más importantes que he aprendido trabajando en un sistema altamente concurrente es esta: nunca puedes tener suficientes métricas . Las métricas deberían controlar absolutamente todo en su aplicación: no es un ingeniero si no está midiendo todo.

Sin métricas, no puede hacer algunas cosas muy importantes:

  1. Evaluar la diferencia hecha por los cambios en el sistema. Si no sabe si la perilla de ajuste A hizo que la métrica B subiera y la métrica C bajara, no sabrá cómo arreglar su sistema cuando las personas empujan código inesperadamente maligno en su sistema (y empujarán el código a su sistema) .

  2. Comprenda lo que debe hacer a continuación para mejorar las cosas. Hasta que sepa que las aplicaciones se están quedando sin memoria, no puede discernir si debería obtener más memoria o comprar más disco para sus servidores.

Las métricas son tan cruciales y esenciales que he hecho un esfuerzo consciente para planificar lo que quiero medir antes de pensar en lo que requerirá un sistema. De hecho, las métricas son tan cruciales que creo que son la respuesta correcta a esta pregunta: solo se sabe qué se puede hacer secuencial o concurrente cuando se mide lo que están haciendo los bits en su programa. El diseño adecuado utiliza números, no conjeturas.

Dicho esto, ciertamente hay algunas reglas generales:

  1. Secuencial implica dependencia. Dos procesos deben ser secuenciales si uno depende del otro de alguna manera. Los procesos sin dependencias deben ser concurrentes. Sin embargo, planifique una forma de manejar las fallas ascendentes que no impidan que los procesos posteriores esperen indefinidamente.

  2. Nunca mezcle una tarea vinculada de E / S con una tarea vinculada a la CPU en el mismo núcleo. No (por ejemplo) escriba un rastreador web que inicie diez solicitudes simultáneas en el mismo hilo, las raspe tan pronto como entren y espere escalar a quinientas: las solicitudes de E / S van a una cola en paralelo, pero la CPU aún los revisará en serie. (Este modelo impulsado por eventos de un solo subproceso es popular, pero es limitado debido a este aspecto; en lugar de comprender esto, la gente simplemente se retuerce las manos y dice que Node no se escala, para darle un ejemplo).

Un solo hilo puede hacer mucho trabajo de E / S. Pero para usar completamente la simultaneidad de su hardware, use grupos de subprocesos que juntos ocupen todos los núcleos. En el ejemplo anterior, el lanzamiento de cinco procesos Python (cada uno de los cuales puede usar un núcleo en una máquina de seis núcleos) solo para el trabajo de la CPU y un sexto hilo Python solo para el trabajo de E / S se escalará mucho más rápido de lo que piensa.

La única forma de aprovechar la concurrencia de la CPU es a través de un conjunto de subprocesos dedicado. Un solo subproceso suele ser lo suficientemente bueno para un montón de trabajo de E / S. Esta es la razón por la cual los servidores web controlados por eventos como Nginx escalan mejor (hacen un trabajo limitado de E / S) que Apache (que combina el trabajo vinculado de E / S con algo que requiere CPU y lanza un proceso por solicitud), pero por qué usar Node para realizar Decenas de miles de cálculos de GPU recibidos en paralelo es una idea terrible .


0

Bueno, para el proceso de verificación, cuando diseño un sistema concurrente grande, tiendo a probar el modelo usando LTSA - Analizador de sistema de transición etiquetado . Fue desarrollado por mi antiguo tutor, que es un veterano en el campo de la concurrencia y ahora es Jefe de Computación en Imperial.

En cuanto a determinar qué puede y qué no puede ser concurrente, creo que hay analizadores estáticos que podrían mostrarlo, aunque tiendo a dibujar diagramas de programación para secciones críticas, igual que lo haría para la gestión de proyectos. Luego identifique las secciones que realizan la misma operación de forma repetitiva. Una ruta rápida es solo para encontrar bucles, ya que tienden a ser las áreas que se benefician del procesamiento paralelo.


0

¿Cómo se da cuenta de lo que puede hacerse concurrente frente a lo que tiene que ser secuencial?

Casi todo lo que escribe puede aprovechar la concurrencia, especialmente el caso de uso "devide an conquer". Una pregunta mucho mejor es ¿qué debería ser concurrente?

Threading de Joseph Albahari en C # enumera cinco usos comunes.

Multithreading tiene muchos usos; Aquí están los más comunes:

Mantener una interfaz de usuario receptiva

Al ejecutar tareas que requieren mucho tiempo en un subproceso paralelo de "trabajador", el subproceso principal de la interfaz de usuario es libre de continuar procesando eventos de teclado y mouse.

Hacer un uso eficiente de una CPU bloqueada

El subprocesamiento múltiple es útil cuando un subproceso está esperando una respuesta de otra computadora o pieza de hardware. Si bien un subproceso está bloqueado mientras realiza la tarea, otros subprocesos pueden aprovechar la computadora que de otro modo no estaría cargada.

Programación paralela

El código que realiza cálculos intensivos puede ejecutarse más rápido en computadoras multinúcleo o multiprocesador si la carga de trabajo se comparte entre múltiples subprocesos en una estrategia de "divide y vencerás" (ver Parte 5).

Ejecución especulativa

En las máquinas multinúcleo, a veces puede mejorar el rendimiento al predecir algo que deba hacerse y luego hacerlo con anticipación. LINQPad utiliza esta técnica para acelerar la creación de nuevas consultas. Una variación es ejecutar varios algoritmos diferentes en paralelo que resuelven la misma tarea. Cualquiera que termine primero “gana”, esto es efectivo cuando no puede saber de antemano qué algoritmo se ejecutará más rápido.

Permitir que las solicitudes se procesen simultáneamente

En un servidor, las solicitudes de los clientes pueden llegar simultáneamente y, por lo tanto, deben manejarse en paralelo (.NET Framework crea subprocesos para esto automáticamente si usa ASP.NET, WCF, Servicios web o Remoting). Esto también puede ser útil en un cliente (por ejemplo, manejar redes de igual a igual, o incluso múltiples solicitudes del usuario).

Si no está tratando de hacer uno de los anteriores, probablemente sea mejor que lo piense mucho.

¿Cómo reproduce las condiciones de error y ve lo que sucede mientras se ejecuta la aplicación?

Si está usando .NET y ha escrito casos de uso, puede usar CHESS, que puede recrear condiciones específicas de entrelazado de hilos que le permiten probar su solución.

¿Cómo visualiza las interacciones entre las diferentes partes concurrentes de la aplicación?

Depende del contexto. Para escenarios de trabajadores, pienso en un gerente subordinado. El gerente le dice al subordinado que haga algo y espera actualizaciones de estado.

Para tareas simultáneas no relacionadas, pienso en ascensores o automóviles en carriles de tráfico separados.

Para la sincronización, a veces pienso en los semáforos o en los estilos de giro.

Además, si está utilizando C # 4.0, puede echar un vistazo a la Biblioteca paralela de tareas


0

Mi respuesta a esta pregunta es:

  • ¿Cómo se da cuenta de lo que puede hacerse concurrente frente a lo que tiene que ser secuencial?

Primero, necesito saber por qué debería usar la concurrencia, porque he descubierto que la gente se entusiasma con la idea detrás de la concurrencia, pero no siempre pienso en el problema que están tratando de resolver.

Si tiene que simular una situación de la vida real como colas, flujos de trabajo, etc., lo más probable es que necesite usar un enfoque concurrente.

Ahora que sé que debería usarlo, es hora de analizar el intercambio, si tiene muchos procesos, puede pensar en la sobrecarga de la comunicación, pero si tiene que hacerlo, puede terminar sin una solución concurrente (vuelva a analizar el problema si asi que.)

  • ¿Cómo reproduce las condiciones de error y ve lo que sucede mientras se ejecuta la aplicación?

No soy un experto en este asunto, pero creo que para los sistemas concurrentes este no es el enfoque correcto. Se debe elegir un enfoque teórico, buscando los 4 requisitos de punto muerto en áreas críticas:

  1. No preventivo
  2. Espera y espera
  3. Exclusión motriz
  4. Cadena circular

    • ¿Cómo visualiza las interacciones entre las diferentes partes concurrentes de la aplicación?

Primero trato de identificar quiénes son los participantes en las interacciones, luego cómo se comunican y con quién. Finalmente, los gráficos y diagramas de interacción me ayudan a visualizar. Mi vieja pizarra no puede ser superada por ningún otro tipo de medio.


0

Seré directo. Adoro las herramientas. Yo uso muchas herramientas. Mi primer paso es establecer los caminos previstos para el flujo de estado. Mi próximo paso es tratar de averiguar si vale la pena, o si el flujo de información requerido hará que el código sea serial con demasiada frecuencia. Luego, intentaré redactar algunos modelos simples. Estos pueden variar desde una pila de esculturas crudas de palillos de dientes hasta algunos ejemplos similares simples en python. Luego, miro algunos de mis libros favoritos, como el pequeño libro de semáforos, y veo si alguien ya ha encontrado una mejor solución para mi problema.

Entonces empiezo a codificar.
Es una broma. Un poco más de investigación primero. Me gusta sentarme con un compañero pirata informático y analizar la ejecución esperada del programa a un alto nivel. Si surgen preguntas, pasamos a un nivel inferior. Es importante averiguar si alguien más puede entender su solución lo suficientemente bien como para mantenerla.

Finalmente, empiezo a codificar. Intento mantenerlo muy simple primero. Solo la ruta del código, nada lujoso. Mueve el menor estado posible. Evita las escrituras. Evite las lecturas que puedan entrar en conflicto con las escrituras. Evite, sobre todo, las escrituras que puedan entrar en conflicto con las escrituras. Es muy fácil descubrir que tiene un número positivamente tóxico de estos, y que su hermosa solución es repentinamente poco más que un enfoque en serie que destruye el caché.

Una buena regla es usar marcos donde sea que pueda. Si está escribiendo componentes de subprocesos básicos usted mismo, como buenas estructuras de datos sincronizados, o sin dios, sincronicitimitivos reales, es casi seguro que va a volar toda su pierna.

Finalmente, herramientas. La depuración es muy difícil. Uso valgrind \ callgrind en linux junto con PIN y estudios paralelos en windows. No intentes depurar esto a mano. Probablemente puedas. Pero probablemente desearías no haberlo hecho. Diez horas dominando algunas herramientas poderosas, y algunos buenos modelos te ahorrarán cientos de horas más tarde.

Por encima de todo, trabaje de forma incremental. Trabaja con cuidado. No escriba código concurrente cuando esté cansado. No lo escriba mientras tenga hambre. De hecho, si puede evitarlo, simplemente no lo escriba. La concurrencia es difícil, y he descubierto que muchas aplicaciones que lo enumeran como una característica a menudo se entregan con ella como su única característica.

En resumen:
Comience:
piense,
hable
,
escriba, pruebe, simplemente
lea
,
escriba , escriba , escriba,
depure,
vaya a Comenzar

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.