Está en la página 1de 17

Linux y SMP (procesamiento

simétrico)
Play Linux en el sistema SMP

El rendimiento del sistema Linux se puede mejorar de varias maneras, y el más popular es
mejorar el rendimiento del procesador. Una solución obvia es usar un procesador con una
frecuencia de reloj más rápida, pero hay un límite físico para cualquier técnica en
particular, y la frecuencia del reloj tiene tal límite. Al alcanzar ese límite, puede usar el
multiprocesador utilizando un método "Más". Desafortunadamente, el rendimiento de los
multiprocesadores no es una relación lineal con un solo rendimiento del procesador.

Antes de discutir las solicitudes de multiprocesamiento en Linux, revisaremos rápidamente


la historia de más procesamiento.

Historial multi-proceso
Algunas empresas se originaron a mediados de la década de 1950, algunas de las cuales
puede saber, mientras que otras pueden no recordar (IBM, Digital Equipment Corporation,
Control Data Corporation). A principios de la década de 1960, Burroghs Corporation
introdujo un multiprocesador simétrico MIMD con cuatro CPU y accesibles hasta dieciséis
módulos de memoria (primera arquitectura SMP) por interruptores de cruce. En 1964, se
introdujo CDC 6600, y su uso fue exitoso y popular, que proporcionó una CPU con diez
sub-procesadores (unidades de procesamiento periférico). A fines de la década de 1960,
Honeywell lanzó su primer sistema de múltiples múltiples, que es otro sistema simétrico de
multiprocesamiento con ocho CPU.

Mientras desarrolla sistemas de multiprocesamiento, el uso de varias tecnologías también


aumenta la capacidad de reducir el volumen del procesador y las frecuencias de reloj más
rápidas. En la década de 1980, Cray Research y otras compañías introdujeron sistemas
multiprocesadores y sistemas operativos UNIX® (CX-OS) para aprovechar estas
capacidades.
A fines de la década de 1980, con la popularidad de un sistema informático personal de un
solo procesador (como IBM PC), el uso de sistemas de multiprocesamiento disminuyó.
Pero para 20 años después, el multiprocesamiento utiliza tecnología de multiprocesamiento
simétrico para volver a un sistema informático personal.

Amdahl
Gene Amdahl es un arquitecto informático, el personal de IBM, en IBM, Amdahl
Corporation (un negocio nombrado en su nombre) y otras compañías involucradas en el
desarrollo de la arquitectura informática. Pero lo más famoso es su regla, que se utiliza para
predecir la mejoría máxima del sistema esperada después del sistema mejorado. Se utiliza
principalmente para calcular la mayor mejora del rendimiento en el uso teórico de los
multiprocesadores (consulte la Figura 1).

Figura 1. Regla de paralelismo del procesador Amdahl

Usando la ecuación que se muestra en la Figura 1, se puede calcular la mejora máxima de


rendimiento del sistema.NIndica el número de procesadores y el factor.FEspecifica la parte
del sistema (es decir, la parte del sistema del orden en la naturaleza). El resultado se
muestra en la Figura 2.

Figura 2. Ley de Amdahl de hasta diez CPU


La línea superior en la Figura 2 muestra el número de procesadores. Idealmente, agregue
otro procesador para resolver el problema, espero ver tal crecimiento de desempeño.
Desafortunadamente, no todos los problemas pueden ser paralelos, y también hay una
sobrecarga del procesador de gestión, por lo que el aumento rápido no es tan grande. La
parte inferior (línea púrpura) es un procesamiento del 90% de ejemplos de problemas
secuenciales. En esta figura, el mejor caso es la línea marrón, que muestra un problema del
10% de secuencialidad (por lo que el 90% en comparación). Incluso en este caso, el
rendimiento de ejecución de diez procesadores es solo un poco mejor que cinco.

Multi-proceso y PC
Arquitectura SMP: dos o más los mismos procesadores están conectados entre sí a través de
una memoria compartida. Cada procesador puede acceder a la memoria compartida por
igual (con el mismo retardo de acceso de espacio de memoria). Esta arquitectura se puede
comparar con la arquitectura de acceso a la memoria (NUMA) no uniformes. Por ejemplo,
cada procesador tiene su propia memoria y tiene diferentes retrasos de acceso al acceder a
la memoria compartida.

Acoplamiento flojo
El primer sistema Linux SMP está suelto acoplando múltiples sistemas de procesadores.
Estos sistemas se construyen con múltiples sistemas individuales interconectados de alta
velocidad (como 10G Ethernet, canal de fibra o infiniband). Dichas arquitecturas también
se conocen como grupos (véase la Figura 3), y el proyecto Linux Beowulf es una solución
popular para dicha arquitectura. El clúster Linux Beowulf se puede construir utilizando el
hardware normal y las interconexiones de red típicas (como Ethernet).

Figura 3. Acoplamiento suelto de la arquitectura de procesamiento múltiple

Construir una arquitectura multi-procesador acoplada suelta es fácil (debido a proyectos


como Beowulf), también tienen sus propias restricciones. La construcción de una gran red
multiprocesador puede ocupar un espacio considerable y consumir mucho poder. Debido a
que generalmente se construyen con hardware ordinario, algún hardware que contiene no
está relacionado con mucho poder y espacio. Una desventaja más grande es la estructura de
la comunicación. Incluso con redes de alta velocidad (como 10G Ethernet), también hay un
límite para la escalabilidad del sistema.

Multi-proceso acoplado apretado


Multi-proceso estrechamente acoplado se refiere al multiprocesamiento de nivel de chip
(CMP). Puede considerarse como una arquitectura de acoplamiento suelto a escala al nivel
de chip. Esta es la idea de que se acopla estrechamente con múltiples procesos (también
conocidos como cálculos de múltiples núcleos). En un circuito integrado, una pluralidad de
chips, memoria compartida e interconexiones forman un núcleo de procesamiento múltiple
de primer plano (consulte la Figura 4).

Figura 4. Arquitectura multi-proceso acoplada hermética

En CMP, las CPU múltiples están conectadas a la memoria compartida (2 caché) a través
del bus compartido. Cada procesador también tiene su propia memoria rápida (caché de
nivel 1). La esencia de CMP está estrechamente acoplada a la distancia física entre el
procesador y la memoria, por lo que puede proporcionar el retardo de acceso de memoria
más pequeño y un mayor rendimiento. Dicha arquitectura está funcionando bien en
aplicaciones multi-roscadas, que se pueden asignar a múltiples procesadores en dichas
aplicaciones para implementar operaciones paralelas. Este método se denomina paralelo a
nivel de rosca (TLP).

Debido a la popularidad de esta arquitectura multi-procesador, muchos proveedores han


producido dispositivos CMP. La Tabla 1 enumera algunas variantes populares compatibles
con Linux.

Tabla 1. Muestra de equipos CMP


proveedor equipo Descripción

IBM POWER4 SMP, CPU dual

IBM POWER5 SMP, CPU dual, cuatro hilos concurrentes

AMD AMD X2 SMP, CPU dual

Intel® Xeon SMP, CPU dual o cuatro CPU

Intel Core2 Duo SMP, CPU dual

ARM MPCore SMP, hasta cuatro CPUs

IBM Xenon SMP, TRES POWER PC CPUS

IBM Cell Processor Multiprocesamiento asimétrico, ASMP, nueve CPU

Configuración del kernel


Para usar SMP con SMP en el hardware que admite SMP, el kernel debe estar configurado
correctamente. Habilitado durante la configuración del kernelCONFIG_SMPOpciones para
percibir el kernel consciente de SMP. Al ejecutar un SMP percibido en un host multi-CPU,
puede usar el sistema de archivos PROC para comprender el número y el tipo del
procesador.

Primer usogrepRecupere el número de procesadores en el archivo CPUINFO en virtud de /


proc. Como se muestra en la listado 1, use la opción de conteo ( -c) Contar
palabrasprocessorLínea de apertura. Luego mostrarcpuinfoEl contenido del archivo.
Ejemplo de visualización de una placa base Xeon de dos chips.

Listado 1. Use el sistema de archivos PROC para recuperar la información de la CPU

1. mtj@camus:~$ grep -c ^processor /proc/cpuinfo


2. 8
3. mtj@camus:~$ cat /proc/cpuinfo
4. processor :0
5. vendor_id : GenuineIntel
6. cpu family : 15
7. model :6
8. model name : Intel(R) Xeon(TM) CPU 3.73GHz
9. stepping :4
10. cpu MHz : 3724.219
11. cache size : 2048 KB
12. physical id : 0
13. siblings :4
14. core id :0
15. cpu cores :2
16. fdiv_bug : no
17. hlt_bug : no
18. f00f_bug : no
19. coma_bug : no
20. fpu : yes
21. fpu_exception : yes
22. cpuid level : 6
23. wp : yes
24. flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr
25. pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm
26. pbe nx lm pni monitor ds_cpl est cid xtpr
27.
28. bogomips : 7389.18
29.
30. ...
31.
32. processor :7
33. vendor_id : GenuineIntel
34. cpu family : 15
35. model :6
36. model name : Intel(R) Xeon(TM) CPU 3.73GHz
37. stepping :4
38. cpu MHz : 3724.219
39. cache size : 2048 KB
40. physical id : 1
41. siblings :4
42. core id :3
43. cpu cores :2
44. fdiv_bug : no
45. hlt_bug : no
46. f00f_bug : no
47. coma_bug : no
48. fpu : yes
49. fpu_exception : yes
50. cpuid level : 6
51. wp : yes
52. flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr
53. pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm
54. pbe nx lm pni monitor ds_cpl est cid xtpr
55.
56. bogomips : 7438.33
57.
58. mtj@camus:~$

Kernel SMP y Linux


En los primeros días de Linux 2.0, SMP Support consiste en una "bloqueo grande", que
serializa el acceso a través del sistema. La mejora del soporte de SMP se realiza
lentamente, pero hasta el 2.6 kernel muestra el poder de SMP.

2.6 El kernel presenta un nuevo programador O (1), que contiene un mejor soporte del
sistema SMP. La clave es que el saldo de carga se puede realizar entre las CPU disponibles
mientras se mantiene la afinidad para aumentar la eficiencia del caché. Revise la Figura 4,
cuando la tarea está asociada con una única CPU, si lo mueve a otra CPU, debe vaciar el
caché para la tarea. Esto aumenta el retraso de acceso a la memoria de la tarea, que se
utiliza para mover sus datos a la memoria de la nueva CPU.

2.6 El kernel sirve a dos ratones para cada procesador (luminiscente expirado y activo).
Cada lejaduras admite 140 prioridades, el frente 100 se utiliza para tareas en tiempo real,
mientras que 40 de los siguientes cuatro se utilizan para las tareas de usuario. Plantillas de
división de tareas, cuando se utilizan las películas de tiempo asignadas, estas tareas se
mueven desde la lejada activa hasta la batralue caducada. Esto proporciona un acceso justo
a la CPU para todas las tareas (solo bloqueadas de acuerdo con cada CPU).

Con la cola de tareas de cada CPU, se puede realizar el balance de carga de acuerdo con
todas las cargas de la CPU en el sistema. Cada 200 milisegundos, el programador realiza un
ajuste de equilibrio de carga para reasignar la carga de tareas y mantener el equilibrio entre
el procesador. Para obtener más información sobre los programadores de Linux 2.6,
veaReferencia sección.

Hilo de espacio de usuario: función usando


SMP
Para usar SMP, debe realizar un montón de trabajo en el núcleo de Linux, pero no es
suficiente para confiar en el propio sistema operativo. Mirando hacia atrás, la habilidad de
SMP depende de TLP. Un solo programa de una sola rebanada (no roscada) no puede usar
SMP, pero SMP se puede usar en un programa que consta de múltiples hilos distribuidos
entre núcleos. Cuando un hilo se retrasa esperando el proceso de E / S, otro hilo puede
realizar algún trabajo útil. De esta manera, los hilos se colaboran para ocultar su respectivo
tiempo de retardo.

El hilo de la interfaz del sistema operativo portátil (POSIX) es una buena manera de
construir una aplicación roscada que puede aprovechar SMP. El hilo POSIX proporciona
un mecanismo de roscado y memoria compartida. Cuando el programa de llamadas crea
varios hilos, cada hilo obtiene su propia pila (variables y estado locales), pero compartió el
espacio de datos del hilo principal. Todos los hilos creados compartieron este mismo
espacio de datos, pero el problema está aquí.

Se requieren mecanismos de coordinación para respaldar el intercambio de acceso multi-


roscado. POSIX proporciona un mutex para crearÁrea críticaPara la implementación de
acceso exclusivo a objetos (una pieza de memoria). No lo haga, puede resultar en una falla
de memoria (debido a múltiples hilos que realizan diferentes pasos). El listado 2 demuestra
cómo crear un área crítica utilizando la función Posix Mutex.

Listado 2. Creación de un área crítica usando PTHREAD_MUTEX_LOCK y


PTHREAD_MUTEX_UNLOCK

1. pthread_mutex_t crit_section_mutex = PTHREAD_MUTEX_INITIALIZER;


2.
3. ...
4.
5. pthread_mutex_lock( &crit_section_mutex );
6.
7. /* Inside the critical section. Memory access is safe here
8. * for the memory protected by the crit_section_mutex.
9. */
10.
11. pthread_mutex_unlock( &crit_section_mutex );
Si múltiples subprocesos intentan bloquear la cantidad después de completar la llamada
original, estos hilos se bloquearán y sus solicitudes ingresen la cola hasta la
ejecución.pthread_mutex_unlockTransferencia.

Protección de la variable del núcleo SMP


Si se operan múltiples núcleos en el procesador en el kernel, desea evitar compartir
aquellos específicos de un núcleo determinado. Por esta razón, se introduce el núcleo
2.6.per-CPUEl concepto de variables, estas variables se asocian con una sola CPU. Esto
permite a las variables que una CPU generalmente se accede a la variable patentada de la
CPU. Use este método para minimizar los requisitos de bloqueo y mejorar el rendimiento.

Variable por CPUDEFINE_PER_CPUSe define la macro, que requiere que la macro


proporcione un nombre de tipo y variable. Debido al modo de operación de la macro es
similar al valor L, la inicialización de las variables también se puede realizar en la macro.
El siguiente ejemplo (de ./arch/i386/kernel/smpboot.c) define una variable que representa el
estado de cada CPU en el sistema.

1. /* State of each CPU. */


2. DEFINE_PER_CPU(int, cpu_state) = { 0 };
Esta macro crea una matriz variable (una variable para cada CPU). Para acceder a la
variable PERPU, necesitaper_cpuMacrosmp_processor_idEn combinación, esta última es
una función para devolver el identificador de la CPU actual que actualmente ejecuta el
código.

per_cpu( cpu_state, smp_processor_id() ) = CPU_ONLINE;

El kernel proporciona funciones adicionales para la asignación dinámica de bloqueo y


variables de CPU. Puede encontrar estas funciones en ./include/linux/percpu.h.

Concluir
Cuando la frecuencia del procesador alcanza su límite, un método de rendimiento de mejora
popular es agregar más procesadores. En los primeros días, esto significa agregar más
procesadores a la placa base o establece múltiples clústeres de computación independientes.
Ahora, el procesamiento de múltiples procesos de chip puede proporcionar más CPU en un
solo chip, lo que resulta en un mayor rendimiento debido a la reducción de los retrasos en la
memoria.

Encontrará que el sistema SMP existe no solo en el servidor, sino también en el escritorio,
especialmente después de introducir la virtualización. Como la mayoría de las tecnologías
avanzadas, Linux proporciona soporte SMP. El kernel es responsable de completar la
optimización de la carga entre las CPU disponibles (desde hilos hasta los sistemas
operativos de virtualización). Lo único que debe hacer es asegurarse de que la aplicación
pueda estar completamente multi-roscada para usar SMP.

Procesos e Hilos en C de Unix/Linux


Vamos a ver lo básico de hilos y procesos. Las diferencias entre ellos y algún ejemplo sencillo
de cada. Vermos la función fork() para procesos y los hilos de la librería POSIX.

 Procesos y threads (hilos de ejecución)


 Ejemplo de programación de procesos
 Ejemplo de programación de hilos
Procesos y Threads (hilos de ejecución)

Si queremos que nuestro programa empiece a ejecutar varias cosas "a la vez", tenemos dos
opciones. Por una parte podemos crear un nuevo proceso y por otra, podemos crear un nuevo
hilo de ejecución (un thread). En realidad nuestro ordenador, salvo que tenga varias cpu, no
ejecutará varias cosas a la vez. Cuando digo "a la vez", me refiero a que el sistema operativo
irá ejecutando cachos de programa por turnos (por rodajas de tiempo) de forma muy rápida,
dando la sensación de simultaneidad.

¿Cual es la diferencia entre proceso e hilo?

Un proceso de unix es cualquier programa en ejecución y es totalmente independiente de


otros procesos. El comando de unix ps nos lista los procesos en ejecución en nuestra
máquina. Un proceso tiene su propia zona de memoria y se ejecuta "simultáneamente" a otros
procesos. Es totalemente imposible en unix que un proceso se meta, a posta o por
equivocación, en la zona de memoria de otro proceso. Esta es una de las caracteristicas que
hace de unix un sistema fiable. Un programa chapucero o malintencionado no puede fastidiar
otros programas en ejecución ni mucho menos a los del sistema operativo. Si el programa
chapucero se cae, se cae sólo él.

Dentro de un proceso puede haber varios hilos de ejecución (varios threads). Eso quiere decir
que un proceso podría estar haciendo varias cosas "a la vez". Los hilos dentro de un proceso
comparten todos la misma memoria. Eso quiere decir que si un hilo toca una variable, todos
los demás hilos del mismo proceso verán el nuevo valor de la variable. Esto hace
imprescindible el uso de semáforos o mutex (EXclusión MUTua, que en inglés es al revés,
funciones pthread_mutex...) para evitar que dos threads accedan a la vez a la misma
estructura de datos. También hace que si un hilo "se equivoca" y corrompe una zona de
memoria, todos los demás hilos del mismo proceso vean la memoria corrompida. Un fallo en
un hilo puede hacer fallar a todos los demás hilos del mismo proceso.

Un proceso es, por tanto,  más costoso de lanzar, ya que se necesita crear una copia de toda
la memoria de nuestro programa. Los hilos son más ligeros.

En cuanto a complejidad, en los hilos, al compartir la memoria y los recursos, es casi obligado
el uso de mutex o semáforos, así que su programación suele ser más complicada y se
necesita ser más cuidadoso. Un proceso, en el momento de lanzarlo, se hace independiente
del nuestro, así que no deberíamos tener ningún problema, salvo que necesitemos
comunicación entre ellos, que nos liaríamos a programar memorias compartidas (con sus
correspondientes semáforos), colas de mensajes, sockets o cualquier otro mecanismo de
comunicación entre procesos unix.

 ¿Qué elegimos? ¿Un proceso o un hilo?. Depende de muchos factores, pero yo (y es cosa
mia, que tengo un PC obsoleto con linux), suelo elegir procesos cuando una vez lanzado el
hijo no requiero demasiada comunicación con él. Elijo hilos cuando tienen que compartir y
actualizarse datos. Por ahí he leido que para gestionar entradas/salidas es mejor procesos
(atender simultaneamente a varias entradas de sockets, por ejemplo) y que para hacer
programas con muchos cálculos en paralelo con varias cpu es mejor hilos, siempre y cuando
el sistema operativo sea capaz de repartir automáticamente los hilos en las distintas cpu en
función de su carga de trabajo.
Ejemplo de programación de procesos

Vamos a hacer un pequeño ejemplo de programación de procesos. Intentaremos con el


ejemplo, además de lanzar un nuevo proceso, comprobar que efectivamente las variables "se
duplican". Más adelante veremos cómo comunicar padre e hijo.

La función C que crea un nuevo proceso es fork(). ¡Qué suerte!, tiene mucha miga, pero no
lleva parámetros

Cuando llamamos a fork(), en algún lugar dentro de la función, se duplican los procesos y
empiezan a correr por separado. Cuando llega el momento de retornar de dicha función (y ya
tenemos dos procesos), al proceso original le devuelve un identificador del proceso recien
creado (el mismo numerito que vemos con el comando ps de una shell). Al proceso recien
creado le devuelve 0. Échale un ojo a la figura para entenderlo mejor.

Interioridades de fork() y creación de un nuevo proceso

En rojo el código que habría en nuestro programa.


En azul los procesos que irían corriendo (el tiempo transcurre de arriba a abajo).
El motivo de hacer esto así es que cada proceso pueda saber si es el original (proceso padre)
o el nuevo (proceso hijo) y poder hacer así cosas distintas. Si metemos el fork() en un if, como
en la figura, el proceso original sigue por la parte del else y el nuevo proceso por el then. De
esta manera, el proceso original, por ejemplo, podría seguir atendiendo nuevos clientes que
quieran conectarse a nuestro programa por un socket, mientras que el proceso hijo podría
atender a un cliente que acaba de conectarse y que es el que ha provocado que lancemos
el fork().

A partir de aquí ya podemos programar normalmente. Hay que tener en cuenta que se ha
duplicado todo el espacio de memoria. Por ello, ambos procesos tienen todas las variables
repetidas, pero distintas. Si el proceso original toca la variable "contador", el proceso hijo no
verá reflejado el cambio en su versión de "contador".

Otro tema a tener en cuenta es que si antes del fork() tenemos, por ejemplo, un fichero
abierto (un fichero normal, un socket, una pipa o cualquier otra cosa), después
del fork() ambos procesos tendrán abierto el mismo fichero y ambos podrán escribir en él. Es
más, uno de los procesos puede cerrar el fichero mientras que el otro lo puede seguir teniendo
abierto.

La funcion fork() también puede devolver -1 en caso de error. Si esto ocurre, no se ha creado


ningún nuevo proceso. El ejemplo de la figura no sería válido, porque el if ( fork() ), en caso
de devolver -1, se iría por el else, pero en realidad no se ha creado ningún proceso hijo. Hay
dos posibilidades: meter el fork() en un switch o guardarse la vuelta de fork() en una variable
y luego hacer varios if. El ejemplo del switch sería

switch (fork())
{
    case -1:
        /* Código de error */
        ...
        break;
    case 0:
        /* Código del proceso hijo */
        ...
        break;
    default:
        ...
        /* Código del proceso original */
}

Una vez lanzado el proceso hijo, el padre puede "esperar" que el hijo termine. Para ello
tenemos la función wait(). A la función wait() se le pasa la dirección de un entero para que
nos lo devuelva relleno. La función wait() deja dormido al proceso que la llama hasta que
alguno de sus procesos hijo termina, es decir, si llamamos a wait(), nos quedamos ahí
parados hasta que el hijo termine. A la salid, en el entero tendremos guardada información de
cómo ha terminado el hijo (ha llamado a un exit(), alguien le ha matado, se ha caido, etc).

En el ejemplo, al llamar a wait() ...

int estadoHijo;
...
wait (&estadoHijo);
... el proceso original (padre), se queda dormido hasta que el nuevo proceso (hijo) termina.

Una vez que salimos del wait(), tenemos unas macros que nos permiten evaluar el contenido
de estadoHijo. Hay varias, pero un par de ellas más o menos útiles son:

 WIFEXITED(estadoHijo) es 0 si el hijo ha terminado de una manera anormal (caida,


matado con un kill, etc). Distinto de 0 si ha terminado porque ha hecho una llamada a
la función exit()
 WEXITSTATUS(estadoHijo) devuelve el valor que ha pasado el hijo a la
función exit(), siempre y cuando la macro anterior indique que la salida ha sido por
una llamada a exit().

Por ello, podemos hacer algo como

if ( WIFEXITED(estadoHijo) != 0)
{
    printf ("Mi hijo ha hecho exit (%d)\n", WEXITSTAUS (estadoHijo));
}

En el código de ejemplo pfork.c tienes exactamente esto. Puedes descargarlo, quitarle la


extensión .txt, compilarlo con make pfork (sin Makefile) o con gcc pfork.c -o pfork. Si lo
ejecutas verás en pantalla lo que va pasando. En otra terminal y siendo un poco hábil, puedes
poner ps para ver los procesos que van corriendo. Verás que primero hay un pfork y que
luego hay dos.

Además se ha declarado en el código una variable int variable = 1. El proceso hijo cambia el


valor de dicha variable por un valor 2 y sale. El padre espera que muera el hijo y escribe en
pantalla el valor del exit() del hijo y el valor de la variable, que para él permanece inalterada,
sigue siendo 1.

El siguiente paso es ver cómo comunicar padre e hijo.

Ejemplo de programación de Hilos

Como ejemplo de hilos, vamos a hacer un programa que cree un hilo. Luego, tanto el
programa principal como el hilo se meterán en un bucle infinito. El primero se dedicará a
incrementar un contador y escribir su valor en pantalla. El hilo decrementará el mismo
contador y escribirá su valor en pantalla. El resultado es que veremos en pantalla el contador
incrementándose y decrementándose a toda velocidad.

Mas adelante veremos cómo hacer que un hilo espere a otro o como sincronizarlos para
acceso a estructuras de datos.

La función que nos permite crear un un nuevo hilo de ejcución es pthread_create() que


admite cuatro parámetros:

 pthread_t * es un puntero a un identificador de thread. La función nos devolverá este


valor relleno, de forma que luego podamos referenciar al hilo para "hacerle cosas",
como matarlo, esperar por él, etc.
 pthread_attr_t * son los atributos de creación del hilo. Hay varios atributos posibles,
como por ejemplo la prioridad. Un hilo de mayor prioridad se ejecutará con preferencia
(tendrá más rodajas de tiempo) que otros hilos de menor prioridad. Se puede
pasar NULL, con lo que el hilo se creará con sus atributos por defecto y para nuestro
ejemplo es suficiente. Si queremos un programa que cree y destruya hilos
continuamente, no vale NULL, ya que con esta opción dejaremos memoria sin liberar
cada vez que termine un hilo.
 void *(*)(void *) es un tipo la mar de raro. Aunque asuste, no es más que el tipo de
una función que admite un puntero void * y que devuelve void *. Eso quiere decir que
a este parámetro le podemos pasar el nombre de una función que cumpla lo que
acabamos de decir. Esta función es la que se ejecutará como un hilo aparte. El hilo
terminará cuando la función termine o cuando llame a la función pthread_exit() (o que
alguien lo mate desde otra parte del código). Es bastante habitual hacer que esta
función se meta en un bucle infinito y quede suspendida en un semáforo o a la espera
de una señal para hacer lo que tenga que hacer y volver a quedar dormida.
 void * es el parámetro que se le pasará a la función anterior cuando se ejecute en el
hilo aparte. De esta manera nuestro programa principal puede pasar un único
parámetro (que puede ser cualquier cosa, como una estructura compleja) a la función
que se ejecutará en el hilo. La función del hilo sólo tendrá que hacer el "cast"
adecuado.

void *funcionDelHilo (void *parametro)


{
    EstructuraGorda *miEstructura = (EstructuraGorda *)parametro;
    ...
}

En nuestro ejemplo no le pasaremos ningún parámetro, es decir, pondremos NULL.

La función pthread_create() devuelve 0 si todo ha ido bien. Un valor distinto de 0 si ha habido


algún problema y no se ha creado el thread.

El código de creación del thread quedaría.

void *funcionDelThread (void *);


...
pthread_t idHilo;
...
pthread_create (&idHilo, NULL, funcionDelThread, NULL);

Ya está creado el thread. Ahora nuestro programa principal seguirá a lo suyo y


la funciónDelThread() se estará ejecutando "simultáneamente".

 Podemos hacer que un hilo espere por otro. Para ello tenemos la


función pthread_join().
 Podemos hacer que dos o más hilos accedan sincronizadamente a un recurso
común. Para ellto tenemos las funciones pthread_mutex_lock() y similares.

Hay un pequeño detalle a tener en cuenta. En linux de PC todo esto funciona correctamente
(al menos en el mio). Sin embargo, hay otros microprocesadores/sistemas operativos que
requieren que los threads "colaboren" entre ellos. Un ejemplo es Sparc/Solaris. El sistema
operativo pone en marcha uno de los threads y no le quita el control hasta que él lo diga. Los
demás threads quedan parados hasta que el primero "ceda el control". Para ceder el control
suele haber funciones estilo yield(), thr_yield(), pthread_delay_pn(), etc. También se cede el
control si se hacen llamadas que dejen dormido al thread en espera de algo,
como wait(), sleep(), etc. Así que ya sabes, si ejecutas el ejemplo y ves que el contador sólo
se incrementa o sólo se decrementa, quizás haya que poner usleep (1) dentro de los bucles,
después del printf().

Puedes descargar pthread.c, el Makefile, quitarles la extensión .txt y compilar con make


pthread o bien gcc pthread.c -lpthread -o pthread. Necesitas linkar con la libreria
libpthread.a, de ahí la necesidad de Makefile o del -lpthread. Si lo ejecutas verás en pantalla el
valor del contador incrementándose durante un rato, hasta que entre en ejecución el segundo
hilo. A partir de ese momento, se incrementará y decrementará a toda velocidad. Tendrás que
pararlo con Ctrl-C.

También podría gustarte