El sistema operativo Mach emplea un enfoque modesto de paquete de hilos, el llamado paquete de hilos C o C-Threads, en el que se basó el paquete de hilos DCE de OSF. Este paquete presenta las primitivas de hilos del núcleo de una forma clara y sencilla para ser empleadas por el usuario. Constituye por tanto un paquete de hilos de nivel usuario, aunque como se verá puede tener distintas implementaciones respecto al kernel. No tiene toda la potencia de la interfaz del núcleo, pero si la suficiente para el uso normal de los programadores. Está diseñado para ser portable a distintas arquitecturas y sistemas operativos.
Este paquete proporciona seis llamadas para gestionar los hilos de forma directa.
La sincronización entre hilos se realiza mediante mútex y variables de condición. Las primitivas para mútex son: lock, trylock y unlock. También existen primitivas para crear y eliminar mútex. Las operaciones sobre variables de condición son signal, wait y broadcast. Las primitivas y su funcionamiento son similares a las primitivas para mútex del paquete de hilos DCE de OSF.
Existen varias implementaciones de Hilos C en Mach con respecto al reflejo que tendrán los hilos en el núcleo de Mach :
1) La implementación original realizaba todo su trabajo en el espacio de usuario, dentro de un único proceso. En este método se empleaba el tiempo compartido distribuyéndolo entre todos los hilos C en un sólo hilo de núcleo. Este método permite implementar el paquete en cualquier sistema operativo que no soporte hilos en el núcleo, como es el caso de UNIX.
Los hilos se ejecutan como corrutinas por lo que no se planifican con prioridades. Además un hilo puede conservar la CPU todo el tiempo que quiera o pueda. Por esta razón la utilización de la llamada yield da la oportunidad de ejecución a otros hilos. El problema de esta implementación, que es un problema común a todos los paquetes de hilos a nivel usuario, es que si un hilo realiza una llamada al sistema con bloqueo, todo el proceso se bloquea, y de esta forma no se puede ejecutar otro hilo del proceso, perdiéndose cierto grado de paralelismo. Como en esta implementación no existe un paralelismo verdadero entre los hilos de un proceso de usuario, las distintas ejecuciones son reproducibles, lo cual es muy útil en depuración.
2) Una segunda implementación consiste en utilizar un hilo del núcleo por cada hilo C. Estos hilos se planifican sin prioridades.
En un multiprocesador los hilos se pueden ejecutar realmente en paralelo en distintas CPUs. Se puede realizar una conexión multiplexada de m hilos C con n hilos del núcleo, pero normalmente m = n. Esta implementación es la de mayor utilidad práctica y la más habitual en los sistemas.
3) La tercera implementación consiste en asignar un hilo del núcleo por cada proceso de usuario (con un sólo hilo C), pero configurando los procesos de tal modo que sus espacios de direcciones compartan la misma memoria física.
Es un solución un tanto enrevesada pero permite
compartir la memoria de la misma forma que las implementaciones
anteriores. La desventaja es que aunque se comparte la memoria
entre los procesos de usuario, no se pueden compartir los puertos,
archivos de UNIX y otros recursos exclusivos a cada proceso, lo
que limita su valor práctico.