thread multi ejemplos definicion multithreading language-agnostic

multithreading - multi - ¿Qué son los DO y DONT de subprocesos múltiples?



multi thread definicion (18)

Estoy aplicando mi nuevo conocimiento de hilos en todas partes y obteniendo muchas sorpresas

Ejemplo:

Utilicé hilos para agregar números en una matriz. Y el resultado fue diferente cada vez. El problema fue que todos mis hilos estaban actualizando la misma variable y no estaban sincronizados.

  • ¿Cuáles son algunos problemas conocidos de hilos?
  • ¿Qué cuidado se debe tener al utilizar hilos?
  • ¿Cuáles son los buenos recursos de subprocesos múltiples.
  • Por favor, proporcione ejemplos.

nota al margen:
( thread_add.java nombre de mi programa thread_add.java a thread_random_number_generator.java :-)


Estoy aplicando mi nuevo conocimiento de hilos en todas partes

[Énfasis añadido]

Recuerde que un poco de conocimiento es peligroso. Conocer la API de subprocesos de su plataforma es muy fácil. Saber por qué y cuándo necesita usar la sincronización es la parte difícil. Al leer sobre "puntos muertos", "condiciones de carrera", "inversión de prioridad", comenzarás a entender por qué.

Los detalles de cuándo usar la sincronización son simples (sincronización de necesidades de datos compartidos) y complejos (los tipos de datos atómicos utilizados de la manera correcta no necesitan sincronización, la información que realmente se comparte): una vida de aprendizaje y soluciones muy específicas.


- ¿Cuáles son algunos problemas conocidos de hilos? -

- ¿Qué cuidado se debe tener al utilizar hilos? -

El uso de subprocesos múltiples en una máquina con un solo procesador para procesar múltiples tareas en las que cada tarea lleva aproximadamente el mismo tiempo no siempre es muy efectivo. Por ejemplo, puede decidir generar diez subprocesos dentro de su programa para procesar diez tareas separadas. Si cada tarea demora aproximadamente 1 minuto en procesarse y utiliza diez subprocesos para realizar este proceso, no tendrá acceso a ninguno de los resultados de la tarea durante los 10 minutos completos. Si, en cambio, procesó las mismas tareas utilizando un solo hilo, vería el primer resultado en 1 minuto, el siguiente resultado 1 minuto más tarde, y así sucesivamente. Si puede utilizar cada resultado sin tener que confiar en que todos los resultados estén listos simultáneamente, el único hilo podría ser la mejor manera de implementar el programa.

Si inicia una gran cantidad de subprocesos dentro de un proceso, la sobrecarga de mantenimiento de subprocesos y el cambio de contexto puede ser significativo. El procesador pasará un tiempo considerable cambiando entre subprocesos, y muchos de los subprocesos no podrán avanzar. Además, un solo proceso con un gran número de subprocesos significa que los subprocesos en otros procesos se programarán con menos frecuencia y no recibirán una parte razonable del tiempo del procesador.

Si varios subprocesos tienen que compartir muchos de los mismos recursos, es poco probable que vea los beneficios de rendimiento de los subprocesos múltiples de su aplicación. Muchos desarrolladores ven los subprocesos múltiples como un tipo de varita mágica que proporciona beneficios de rendimiento automático. Desafortunadamente, el multihilo no es la varita mágica que a veces se percibe que es. Si está utilizando subprocesos múltiples por razones de rendimiento, debe medir el rendimiento de su aplicación muy de cerca en varias situaciones diferentes, en lugar de simplemente confiar en una magia inexistente.

Coordinar el acceso de hilos a datos comunes puede ser un gran asesino de rendimiento. Lograr un buen rendimiento con varios subprocesos no es fácil cuando se usa un plan de bloqueo aproximado, ya que esto conduce a una baja concurrencia y subprocesos que esperan acceso. Alternativamente, una estrategia de bloqueo de gran precisión aumenta la complejidad y también puede ralentizar el rendimiento a menos que realice un ajuste sofisticado.

El uso de varios subprocesos para explotar una máquina con varios procesadores suena como una buena idea en teoría, pero en la práctica hay que tener cuidado. Para obtener importantes beneficios de rendimiento, es posible que deba familiarizarse con el equilibrio de subprocesos.

- Por favor, proporcione ejemplos. -

Por ejemplo, imagine una aplicación que recibe información de precios de entrada de la red, agrega y ordena esa información y luego muestra los resultados en la pantalla para el usuario final.

Con una máquina de doble núcleo, tiene sentido dividir la tarea en, por ejemplo, tres hilos. El primer hilo trata sobre el almacenamiento de la información de precios entrante, el segundo hilo procesa los precios y el hilo final controla la visualización de los resultados.

Después de implementar esta solución, suponga que el procesamiento de precios es, con mucho, la etapa más larga, por lo que decide volver a escribir el código de ese hilo para mejorar su rendimiento en un factor de tres. Desafortunadamente, este beneficio de rendimiento en un solo hilo puede no reflejarse en toda la aplicación. Esto se debe a que es posible que los otros dos subprocesos no puedan seguir el ritmo del subproceso mejorado. Si el subproceso de la interfaz de usuario no puede mantenerse al día con el flujo más rápido de información procesada, los otros subprocesos ahora tienen que esperar el nuevo cuello de botella en el sistema.

Y sí, este ejemplo viene directamente de mi propia experiencia :-)


En .Net, una cosa que me sorprendió cuando comencé a intentar ingresar en los subprocesos múltiples es que no se pueden actualizar directamente los controles de la interfaz de usuario desde cualquier hilo que no sea el hilo en el que se crearon los controles de la interfaz de usuario.

Hay una forma de evitar esto, que es usar el método Control.Invoke para actualizar el control en el otro hilo, ¡pero no es 100% obvio la primera vez!


En un entorno de subprocesos múltiples, debe cuidar la sincronización para que dos subprocesos no obstruyan el estado realizando simultáneamente modificaciones. De lo contrario, puede tener condiciones de carrera en su código (por ejemplo, ver el infame accidente de Therac-25 ). También debe programar los subprocesos para realizar diversas tareas. A continuación, debe asegurarse de que su sincronización y programación no provoquen un interbloqueo en el que varios subprocesos se esperarán entre sí de forma indefinida.

Sincronización

Algo tan simple como aumentar un contador requiere sincronización:

counter += 1;

Supongamos esta secuencia de eventos:

  • counter se inicializa a 0
  • el hilo A recupera el counter de la memoria a la CPU (0)
  • cambio de contexto
  • el hilo B recupera el counter de la memoria a la CPU (0)
  • subproceso B aumenta counter en cpu
  • El hilo B escribe el counter de la CPU a la memoria (1)
  • cambio de contexto
  • hilo A aumenta counter en cpu
  • el hilo A vuelve a escribir el counter desde la CPU a la memoria (1)

En este punto, el counter es 1, pero ambos subprocesos intentaron aumentarlo. El acceso al contador se debe sincronizar mediante algún tipo de mecanismo de bloqueo:

lock (myLock) { counter += 1; }

Solo se permite un hilo para ejecutar el código dentro del bloque bloqueado. Dos subprocesos que ejecutan este código podrían resultar en esta secuencia de eventos:

  • el contador se inicializa a 0
  • el hilo A adquiere myLock
  • cambio de contexto
  • el hilo B intenta adquirir myLock pero tiene que esperar
  • cambio de contexto
  • el hilo A recupera el counter de la memoria a la CPU (0)
  • hilo A aumenta counter en cpu
  • el hilo A vuelve a escribir el counter desde la CPU a la memoria (1)
  • hilo A lanza myLock
  • cambio de contexto
  • hilo B adquiere myLock
  • el hilo B recupera el counter de la memoria a la CPU (1)
  • subproceso B aumenta counter en cpu
  • El hilo B escribe el counter de la CPU a la memoria (2)
  • hilo B lanza myLock

En este punto el counter es 2.

Programación

La programación es otra forma de sincronización y debe utilizar mecanismos de sincronización de subprocesos como eventos, semáforos, paso de mensajes, etc. para iniciar y detener subprocesos. Aquí hay un ejemplo simplificado en C #:

AutoResetEvent taskEvent = new AutoResetEvent(false); Task task; // Called by the main thread. public void StartTask(Task task) { this.task = task; // Signal the worker thread to perform the task. this.taskEvent.Set(); // Return and let the task execute on another thread. } // Called by the worker thread. void ThreadProc() { while (true) { // Wait for the event to become signaled. this.taskEvent.WaitOne(); // Perform the task. } }

Notará que el acceso a this.task probablemente no está sincronizado correctamente, que el subproceso de trabajo no puede devolver los resultados al subproceso principal y que no hay forma de indicar que el subproceso de trabajo termine. Todo esto se puede corregir en un ejemplo más elaborado.

Punto muerto

Un ejemplo común de interbloqueo es cuando tiene dos bloqueos y no tiene cuidado de cómo los adquiere. En un punto usted adquiere lock1 antes de lock2 :

public void f() { lock (lock1) { lock (lock2) { // Do something } } }

En otro punto adquiere lock2 antes de lock1 :

public void g() { lock (lock2) { lock (lock1) { // Do something else } } }

Veamos cómo esto podría ser un punto muerto:

  • el hilo A llama f
  • hilo A adquiere lock1
  • cambio de contexto
  • el hilo B llama a g
  • hilo B adquiere lock2
  • El hilo B intenta adquirir lock1 pero tiene que esperar.
  • cambio de contexto
  • el hilo A intenta adquirir lock2 pero tiene que esperar
  • cambio de contexto

En este punto, las hebras A y B se están esperando y están bloqueadas.


Estoy de acuerdo con casi todas las respuestas hasta ahora.

Una buena estrategia de codificación es minimizar o eliminar la cantidad de datos que se comparten entre hilos tanto como sea humanamente posible. Usted puede hacer esto por:

  • Utilizando variables estáticas de subprocesos (aunque no exageres en esto, consumirá más memoria por subproceso, dependiendo de tu O / S).
  • Empaquetando todo el estado utilizado por cada hilo en una clase, luego garantizando que cada hilo obtenga exactamente una instancia de clase de estado para sí mismo. Piense en esto como "enrolle su propio hilo estático", pero con más control sobre el proceso.
  • Calcular datos por valor entre subprocesos en lugar de compartir los mismos datos. Haga que sus clases de transferencia de datos sean inmutables, o garantice que todas las llamadas entre hilos sean sincrónicas, o ambas cosas.

Intente no tener varios subprocesos que compitan por el mismo "recurso" de E / S, ya sea un archivo de disco, una tabla de base de datos, una llamada de servicio web o lo que sea. Esto causará contención cuando varios subprocesos luchan por el mismo recurso.

Aquí hay un ejemplo OTT extremadamente artificial. En una aplicación real, limitaría la cantidad de subprocesos para reducir la sobrecarga de programación:

  • Toda la interfaz de usuario - un hilo.
  • Fondo calcs - un hilo.
  • Registro de errores en un archivo de disco - un hilo.
  • Llamando a un servicio web - un hilo por host físico único.
  • Consultar la base de datos: un hilo por grupo independiente de tablas que necesitan actualización.

En lugar de adivinar cómo dividir las tareas, perfilar su aplicación y aislar los bits que son (a) muy lentos, y (b) podrían hacerse de forma asíncrona. Esos son buenos candidatos para un hilo separado.

Y esto es lo que debes evitar:

  • Cálculos, visitas a la base de datos, llamadas de servicio, etc., todo en un solo hilo, pero multiplicado varias veces "para mejorar el rendimiento".

Haría una declaración muy descarada:

NO utilice la memoria compartida.

Use el paso de mensajes.

Como consejo general, intente limitar la cantidad de estado compartido y prefiera más arquitecturas basadas en eventos.


Hay dos tipos de personas que no utilizan subprocesos múltiples.

1) Aquellos que no entienden el concepto y no tienen idea de cómo programarlo. 2) Aquellos que entienden completamente el concepto y saben lo difícil que es hacerlo bien.


Me doy cuenta de que estás escribiendo en java y que nadie más mencionó libros, por lo que Java Concurrency In Practice debería ser tu biblia de múltiples hilos.


Me sorprende que nadie haya señalado todavía las columnas de Concurrencia Efectiva de Herb Sutter. En mi opinión, esta es una lectura obligada si desea ir a cualquier lugar cerca de hilos.


No comience nuevos hilos a menos que realmente lo necesite. Iniciar subprocesos no es barato y, para las tareas de ejecución corta, el inicio del subproceso puede llevar más tiempo que ejecutar la tarea en sí. Si está en .NET, eche un vistazo al grupo de subprocesos integrado, que es útil en muchos casos (pero no en todos). Al reutilizar los hilos, se reduce el costo de los hilos iniciales.

EDITAR: Algunas notas sobre la creación de subprocesos en comparación con el uso de grupo de subprocesos (específico de .NET)

Generalmente tratar de usar el conjunto de hilos. Excepciones:

  • Las tareas de larga duración vinculadas a la CPU y las tareas de bloqueo no son ideales para ejecutar en el grupo de subprocesos porque obligarán al grupo a crear subprocesos adicionales.
  • Todos los subprocesos del grupo de subprocesos son subprocesos de fondo, por lo que si necesita que su subproceso esté en primer plano, debe iniciarlo usted mismo.
  • Si necesitas un hilo con diferente prioridad.
  • Si su hilo necesita más (o menos) que el espacio de pila estándar de 1 MB.
  • Si necesita poder controlar el tiempo de vida del hilo.
  • Si necesita un comportamiento diferente para crear subprocesos que el ofrecido por el conjunto de subprocesos (por ejemplo, el conjunto limitará la creación de nuevos subprocesos, que pueden o no ser lo que desea).

Probablemente hay más excepciones y no estoy diciendo que esta sea la respuesta definitiva. Es justo lo que podría pensar de la atmósfera.


No puedo darte ejemplos además de apuntarte a Google. Busque los conceptos básicos de subprocesos, la sincronización de subprocesos y obtendrá más resultados de los que sabe.

El problema básico con el enhebrado es que los hilos no se conocen entre sí, por lo que con mucho gusto se pisarán los dedos de los demás, como 2 personas que intentan atravesar 1 puerta, a veces pasarán una detrás de la otra, pero a veces lo harán. Ambos intentan pasar al mismo tiempo y se atascarán. Esto es difícil de reproducir, difícil de depurar y, a veces, causa problemas. Si tiene subprocesos y ve fallas "aleatorias", este es probablemente el problema.

Así que hay que tener cuidado con los recursos compartidos. Si usted y su amigo quieren un café, pero solo hay una cucharada que ambos no pueden usar al mismo tiempo, uno de ustedes tendrá que esperar por el otro. La técnica utilizada para "sincronizar" este acceso a la cuchara compartida está bloqueada. Asegúrate de obtener un bloqueo en el recurso compartido antes de usarlo, y luego lo sueltas. Si alguien más tiene la cerradura, espere hasta que la suelte.

El siguiente problema viene con esos bloqueos, a veces puedes tener un programa complejo, tanto que obtienes un bloqueo, haces otra cosa y luego accedes a otro recurso y tratas de obtener un bloqueo para eso, pero otro hilo tiene ese segundo recurso, así que siéntate y espera ... pero si ese segundo hilo está esperando el bloqueo que tienes para el primer recurso ... se sentará y esperará. Y tu aplicación se queda ahí. Esto se llama punto muerto, 2 hilos que se esperan el uno al otro.

Esos 2 son la gran mayoría de los problemas de hilos. La respuesta generalmente es bloquear durante el menor tiempo posible, y solo mantener 1 bloqueo a la vez.


No se deje engañar pensando que entiende las dificultades de la concurrencia hasta que haya dividido su cabeza en un proyecto real.

Todos los ejemplos de puntos muertos, bloqueos, sincronización, etc., parecen simples, y lo son. Pero te engañarán, porque la "dificultad" en la implementación de la concurrencia de la que todos hablan es cuando se usa en un proyecto real, donde no controlas todo.


Piense en cómo probará su código y reserve un montón de tiempo para esto. Las pruebas unitarias se vuelven más complicadas. Es posible que no pueda probar manualmente su código, al menos no de manera confiable.

Piense en el tiempo de vida del hilo y cómo saldrán los hilos. No mates los hilos. Proporcionar un mecanismo para que salgan con gracia.

Agregue algún tipo de registro de depuración a su código, para que pueda ver que sus hilos se están comportando correctamente tanto en el desarrollo como en la producción cuando las cosas se descomponen.

Use una buena biblioteca para manejar subprocesos en lugar de lanzar su propia solución (si puede). Ej. Java.util.concurrency

NO asuma que un recurso compartido es seguro para subprocesos.

No lo hagas Por ejemplo, use un contenedor de aplicaciones que pueda solucionar los problemas de subprocesos por usted. Usa la mensajería.


Si bien sus diferencias iniciales en sumas de números son, como han señalado varios encuestados, probablemente sea el resultado de una falta de sincronización, si profundiza en el tema, tenga en cuenta que, en general, no podrá reproducir exactamente los resultados numéricos que obtienes en un programa en serie con los de una versión paralela del mismo programa. La aritmética de punto flotante no es estrictamente conmutativa, asociativa o distributiva; diablos, ni siquiera está cerrado.

Y me gustaría discrepar con lo que, creo, es la opinión de la mayoría aquí. Si está escribiendo programas de múltiples subprocesos para un escritorio con una o más CPU de múltiples núcleos, entonces está trabajando en una computadora con memoria compartida y debe abordar la programación de la memoria compartida. Java tiene todas las características para hacer esto.

Sin saber mucho más sobre el tipo de problema que está abordando, dudaría en escribir "debería hacer esto" o "no debería hacer eso".



a) Siempre haga solo 1 hilo responsable de la vida útil de un recurso. De esa manera, el hilo A no eliminará un recurso que B necesita, si B tiene la propiedad del recurso

b) esperar lo inesperado


YAGNI

Lo más importante que debes recordar es: ¿realmente necesitas multihilo?


NO uses variables globales

NO utilice muchos bloqueos (en el mejor de los casos ninguno en absoluto, aunque es prácticamente imposible)

NO intentes ser un héroe, implementando sofisticados protocolos de MT difíciles

Use paradigmas simples. Es decir, compartir el procesamiento de una matriz en n segmentos del mismo tamaño, donde n debe ser igual al número de procesadores

Pruebe su código en diferentes máquinas (usando uno, dos, muchos procesadores)

Use operaciones atómicas (como InterlockedIncrement() y similares)