2011-05-30 16 views
9

Al usar pthread, puedo pasar datos en el momento de creación del hilo.cómo pasar datos al hilo en ejecución

¿Cuál es la forma correcta de pasar datos nuevos a un hilo que ya se está ejecutando?

Estoy considerando hacer una variable global y hacer que mi hilo sea leído a partir de eso.

Gracias

+7

Una vez más, las personas intentan cerrar las preguntas porque son preguntas para principiantes. Trate de recordar que alguna vez fue un principiante. –

Respuesta

6

Eso sin duda funcionará. Básicamente, los hilos son solo procesos livianos que comparten el mismo espacio de memoria. Las variables globales, al estar en ese espacio de memoria, están disponibles para cada hilo.

El truco no está tanto en los lectores como en los escritores. Si tiene un trozo simple de memoria global, como un int, entonces la asignación a ese int probablemente sea segura. Bt considere algo un poco más complicado, como un struct. Sólo para estar definida, vamos a decir que tenemos

struct S { int a; float b; } s1, s2; 

Ahora s1,s2 son variables de tipo struct S. Podemos inicializarlas

s1 = { 42, 3.14f }; 

y podemos asignarlos

s2 = s1; 

Pero cuando les asignamos el procesador no está garantizada para completar la asignación a toda la estructura en un solo paso - que decir que es no atomic. Así Imaginemos ahora dos hilos:

thread 1: 
    while (true){ 
     printf("{%d,%f}\n", s2.a, s2.b); 
     sleep(1); 
    } 

thread 2: 
    while(true){ 
     sleep(1); 
     s2 = s1; 
     s1.a += 1; 
     s1.b += 3.14f ; 
    } 

Podemos ver que es de esperar que s2 tener los valores {42, 3.14}, {43, 6.28}, {44, 9.42} ....

pero lo que vemos impresa puede ser cualquier cosa como

{42,3.14} 
{43,3.14} 
{43,6.28} 

o

{43,3.14} 
{44,6.28} 

y así sucesivamente. El problema es que el subproceso 1 puede obtener el control y "mirar" s2 en cualquier momento durante esa asignación.

La moraleja es que, si bien la memoria global es una forma perfectamente viable de hacerlo, debe tener en cuenta la posibilidad de que sus hilos se crucen entre sí. Hay varias soluciones para esto, siendo la básica utilizar los semáforos . Un semáforo tiene dos operaciones, denominadas confusamente de holandés como P y V.

P simplemente espera hasta que una variable sea 0 y continúa, agregando 1 a la variable; V resta 1 de la variable. Lo único especial es que lo hacen atómicamente - no se pueden interrumpir.

Ahora, usted código como

thread 1: 
    while (true){ 
     P(); 
     printf("{%d,%f}\n", s2.a, s2.b); 
     V(); 
     sleep(1); 
    } 

thread 2: 
    while(true){ 
     sleep(1); 
     P(); 
     s2 = s1; 
     V(); 
     s1.a += 1; 
     s1.b += 3.14f ; 
    } 

y tiene la garantía de que nunca tendrá rosca 2 media completar una tarea, mientras que el hilo 1 está tratando de imprimir.

(Pthreads tiene semáforos, por cierto.)

+3

@Charlie: lo siento, pero estoy completamente en desacuerdo con la redacción de los hilos como "procesos ligeros". En muchos sistemas operativos, incluidos muchos sistemas unixoid (p. Ej. BSD) y Windows, los hilos son las entidades * que ejecutan el código *, mientras que los procesos son simples contenedores de hilos, el espacio de memoria, etc. Ahora, me doy cuenta de que esto puede ser complicado, pero Creo que la distinción es muy relevante y muy importante. – 0xC0000022L

+2

@SAD También es incorrecto, tanto histórica como fácticamente. Los procesos son espacios de memoria con uno, o con subprocesos, más de un contador de programa. Un hilo es un "hilo de control" que incluye un contador de programa y una pila. Ver, por ejemplo, el artículo de Wiki sobre hilos. http://en.wikipedia.org/wiki/Thread_(computer_science) La gente se confunde ahora, pero un buen libro de sistemas operativos analizará la historia y la aclarará. –

+0

La idea de que "los hilos son solo procesos livianos que comparten el mismo espacio de memoria" es realmente una horrible idea errónea derivada de la implementación original inimaginablemente mala, no compatible con POSIX de "hilos POSIX" en Linux, conocida como LinuxThreads. Es completamente contradictorio con la definición/uso del estándar de las palabras "proceso" e "hilo". –

2

Las variables globales son malas para empezar, y lo que es peor con la programación multi-hilo. En su lugar, el creador del hilo debe asignar algún tipo de objeto de contexto que haya pasado al pthread_create, que contiene los búferes, bloqueos, variables de condición, colas, etc. necesarios para pasar información desde y hacia el hilo.

2

Tendrá que construir esto usted mismo. El enfoque más típico requiere un poco de cooperación del otro hilo, ya que sería una interfaz un poco extraña para "interrumpir" un hilo en ejecución con algunos datos y código para ejecutarlo ... Eso también tendría algunos de los mismos trucos que algo así como señales POSIX o IRQ, que son fáciles de disparar en el pie durante el procesamiento, si no lo has pensado bien ... (Ejemplo simple: no puedes llamar al malloc dentro de un manejador de señal porque podrías ser interrumpido en medio de malloc, por lo que se puede bloquear mientras se accede a malloc 's estructuras de datos internas que son actualizadas de manera parcial.)

el enfoque típico es tener su rutina de creación de hilos básicamente sea un bucle de eventos. Puede construir una estructura de cola y pasarla como argumento a la rutina de creación de subprocesos. Luego, otros subprocesos pueden poner en cola cosas y el ciclo de eventos de la secuencia lo dequeará y procesará los datos. Tenga en cuenta que esto es más limpio que una variable global (o cola global) porque puede escalar para tener varias de estas colas.

Necesitará alguna sincronización en esa estructura de datos de la cola. Se podrían escribir libros completos sobre cómo implementar la sincronización de la estructura de la cola, pero lo más simple sería tener un bloqueo y un semáforo. Al modificar la cola, los hilos toman un bloqueo. Cuando se espera que algo se elimine de la cola, los hilos de los consumidores esperan en un semáforo que se incrementa mediante enqueuers. También es una buena idea implementar algún mecanismo para cerrar el hilo del consumidor.

3

He estado utilizando el mecanismo de comunicación basado en la cola de producción-consumidor, según lo sugerido por asveikau, durante décadas sin ningún problema específicamente relacionado con multiThreading. Hay algunas ventajas:

1) Las instancias 'hiloCommClass' pasadas en la cola a menudo pueden contener todo lo necesario para que el hilo haga su trabajo: miembro/s para datos de entrada, miembro/s para datos de salida, métodos para el Hilo para llamar para hacer el trabajo, en algún lugar para poner cualquier mensaje de error/excepción y un evento 'returnToSender (this)' para llamar, así que devolver algo al solicitante por algún thread-safe significa que el hilo de trabajo no necesita saber. El subproceso de trabajo se ejecuta asincrónicamente en un conjunto de datos totalmente encapsulados que no requiere bloqueo. 'returnToSender (this)' podría poner el objeto en cola en otra cola P-C, podría PostMessage en una secuencia de la interfaz gráfica de usuario, podría liberar el objeto a una agrupación o simplemente eliminarlo(). Lo que sea que haga, el hilo de trabajo no necesita saberlo.

2) No es necesario que el subproceso solicitante sepa nada sobre qué subproceso hizo el trabajo; todo lo que el solicitante necesita es una cola para continuar. En un caso extremo, el hilo de trabajo en el otro extremo de la cola puede serializar los datos y comunicarlos a otra máquina a través de una red, solo llamando a returnToSender (esto) cuando se recibe una respuesta de red - el solicitante no necesita saber esto detalle: solo que el trabajo ya está hecho.

3) Generalmente, es posible organizar las instancias 'threadCommsClass' y las colas para que sobrevivan tanto el hilo del solicitante como el hilo del trabajador. Esto alivia esos problemas cuando el solicitante o el trabajador terminan y se deshacen() antes que el otro, ya que no comparten datos directamente, no puede haber AV/lo que sea. Esto también destruye todos los 'No puedo detener mi hilo de trabajo porque está atorado en un API de bloqueo'. ¿Por qué molestarse en detenerlo si puede quedar huérfano y dejarlo morir sin posibilidad de escribir algo liberado?

4) Un threadpool se reduce a un bucle de una línea para crear varios hilos de trabajo y les pasa la misma cola de entrada.

5) El bloqueo está restringido a las colas. Cuantos más mutexes, condVars, secciones críticas y otros bloqueos sincronizados hay en una aplicación, más difícil es controlarlo todo y mayor es la posibilidad de un interbloqueo intermitente que es una pesadilla para depurar. Con los mensajes en cola, (idealmente), solo la clase de cola tiene bloqueos. La clase de cola debe funcionar al 100% con varios productores/consumidores, pero esa es una clase, no una aplicación llena de bloqueo descoordinado (¡yech!).

6) Un threadCommsClass se puede elevar en cualquier momento, en cualquier lugar, en cualquier hilo y se puede colocar en una cola. Ni siquiera es necesario que el código del solicitante lo haga directamente, por ej. una llamada a un método de clase de registrador, 'myLogger.logString ("Operación completada con éxito");' podría copiar la cadena en un objeto de comunicación, ponerla en cola hasta el hilo que realiza la escritura de registro y devolver 'inmediatamente'. Luego, depende de la secuencia de la clase de registrador manejar los datos de registro cuando los dequeue - puede escribirlos en un archivo de registro, puede encontrar después de un minuto que el archivo de registro es inalcanzable debido a un problema de red. Puede decidir que el archivo de registro es demasiado grande, archivarlo e iniciar otro. Puede escribir la cadena en el disco y luego PostMessage la instancia de threadCommsClass en un hilo de la GUI para mostrar en una ventana de terminal, lo que sea. No tiene importancia para el hilo que solicita el registro, que simplemente continúa, como lo hacen los otros hilos que han solicitado el registro, sin un impacto significativo en el rendimiento.

7) Si necesita matar un hilo esperando en una cola, en lugar de esperar a que el sistema operativo lo mate en la aplicación, solo póngalo en cola y diga que termine.

seguramente hay desventajas:

1) Empujando datos directamente en los miembros de hilo, lo que indica que se ejecute y esperando a que termine es más fácil de entender y será más rápido, en el supuesto de que el hilo no tiene por qué ser creado cada vez

2) Operación realmente asíncrona, donde el hilo se pone en cola algún trabajo y, algún tiempo después, lo devuelve al llamar a un controlador de eventos que tiene que comunicar los resultados, es más difícil de manejar para los desarrolladores utilizados para el código de un solo subproceso y a menudo requiere un diseño de tipo de máquina de estado en el que los datos de contexto se deben enviar en threadCommsClass para que se puedan tomar las acciones correctas cuando vuelvan los resultados. Si hay un caso ocasional en el que el solicitante solo tiene que esperar, puede enviar un evento en el threadCommsClass que se señala mediante el método returnToSender, pero esto es obviamente más complejo que simplemente esperar a que termine algún manejador de subprocesos.

Independientemente del diseño que se use, olvide las simples variables globales como lo han expresado otros carteles. Hay un caso para algunos tipos globales en las comunicaciones de subprocesos; uno que uso muy a menudo es un grupo seguro de subprocesos de instancias de threadCommsClass (esto es solo una cola que se llena previamente con objetos). Cualquier hilo que desee comunicarse tiene que obtener una instancia de threadCommsClass del grupo, cargarla y ponerla en cola. Cuando termina la comunicación, el último subproceso que se usa lo libera al grupo.Este enfoque evita el nuevo fugitivo(), y me permite monitorear fácilmente el nivel del grupo durante la prueba sin ningún administrador de memoria complejo (por lo general, volcar el nivel del grupo a una barra de estado cada segundo con un temporizador). Los objetos que se escapan, (el nivel baja) y los objetos de doble liberación (el nivel sube) se detectan fácilmente y se arreglan.

multihilo puede ser seguro y entregar aplicaciones escalable y de alto rendimiento que son casi un placer para mantener/mejorar, (casi :), pero hay que despedir a las variables globales simples - tratarlos como Tequila - rápido y fácil por ahora, pero sabes que te volarán la cabeza mañana.

¡Buena suerte!

Martin

Cuestiones relacionadas