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.
fuente
Respuestas:
Muchos de los mejores programadores que conozco no terminaron la universidad. En cuanto a mí, estudié filosofía.
sí
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.
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.
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.
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.
fuente
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.
fuente
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
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.
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.
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.
fuente
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 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).
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.
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.
fuente
- Mis respuestas son específicas de MS / Visual Studio -
Eso requerirá conocimiento del dominio, no habrá ninguna declaración general aquí para cubrirlo.
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.
No tengo una buena respuesta a esto, me encantaría ver una.
fuente
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?
fuente
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:
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.
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.
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.
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:
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.
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.
Las desventajas de dicho software incluyen:
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.
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.
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.
Es posible que no siempre le brinden la información que necesita. Esto es un riesgo con todos los intentos de agregar visibilidad.
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.
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.
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.
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:
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) .
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:
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.
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 .
fuente
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.
fuente
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.
Si no está tratando de hacer uno de los anteriores, probablemente sea mejor que lo piense mucho.
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.
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
fuente
Mi respuesta a esta pregunta es:
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.)
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:
Cadena circular
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.
fuente
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
fuente