Las máquinas de estado finito son una herramienta para lograr cierto fin. Como cualquier herramienta, pueden ser abusados también.
Ellos no son el más atento de herramientas, pero el trabajo que son buenos es casi imposible de lograr por otros medios (y por lo general cualquier otro enfoque es entonces condenado a ser un desastre horrible, mil veces peor que la máquina).
El trabajo está funcionando en condiciones en las que los estados de espera clásicos están prohibidos.
Tengo que leer la pantalla táctil. Para leer la posición, tengo que intercambiar unos 15 comandos sobre SPI. Necesito buenas 100 lecturas por segundo. Tengo que esperar aproximadamente 1 microsegundo después de cada comando, para que desaparezca la correspondiente bandera ocupada antes de que pueda continuar. También hay varias otras operaciones que deben poder obtenerse a través de la misma interfaz, como establecer el contraste, cambiar modos, encender o apagar la luz de fondo, leer la temperatura. Si realizara while(BUSY_BIT);
por cada espera, me comería toda la CPU en cuestión de minutos. Si hiciera sched_yield()
o usleep(1)
, nunca alcanzaría la cantidad de lecturas que quiero. La única forma es una máquina de estados finitos.
Pero también hay formas de hacer que la máquina de estados finitos funcione bien. Oculta la máquina detrás de escena y dales a los desarrolladores funciones para trabajar.
Mi experiencia de trabajo hasta ahora estaba dominada por 2 sistemas basados en 3 máquinas de estados finitos diferentes.
- un gran portal web, donde en cada paso recupera algunos datos de la base de datos y, basándose en ello, prepara más consultas. En el último paso, usa los datos para generar HTML. Cada tarea, un módulo de página web, se implementó como una clase PHP que hereda del motor. Estado fue preservado en variables de clase. Cada paso era una función separada. Al final de un paso, las consultas almacenadas se optimizaban y enviaban al motor, a través de cachés, y las respuestas se proporcionaban nuevamente al original.
- un dispositivo integrado con muchos subsistemas. Task Pump se usa. Cada módulo registra un controlador que se llama muchas veces por segundo desde el bucle principal. El controlador puede preservar el estado en variables estáticas o de clase, con estados. Esta multitarea cooperativa permite una memoria mucho más pequeña que ejecutar todo en subprocesos separados, permite la priorización manual de las tareas registrándolas dos veces y hace que el subproceso se ejecute con alta prioridad, eclipsando al resto del sistema.
- semi-intérprete. Esa pantalla táctil. Las llamadas de función y sus estados de espera están registrados, pero cada uno se llama solo una vez y luego se elimina de la cola de programas. El intérprete se llama como una tarea de taskpump, ejecutando un número limitado de funciones hasta encontrar una función marcada como estado de espera (o exceder la cantidad de funciones a ser llamadas). Luego continúa hasta que el estado de espera desaparece. Otras tareas ponen en cola trabajos como (a veces largas) secuencias de funciones para ser ejecutadas, luego esperan el resultado. De esta forma puedo limitar el número de estados que necesito crear a unos 4 donde necesito resultados. Si el comando es "enviar lejos, nunca verificar resultado" como "establecer contraste", no necesitan estados discretos. Por lo tanto, los estados reales son "esperar el evento y registrar los datos solicitados", "esperar la medición" y "leer los resultados y asignarlos correctamente".
El código sería el doble de simple y tres veces más claro si se escribe de forma estructural o secuencial. Excepto que no funcionaría, o funcionaría con un rendimiento abismal.
relacionados: http://stackoverflow.com/questions/1647631/c-state-machine-design – jldupont
Como Pastafarian devotos , el FSM siempre es bueno;) – richo
Nunca maldigas un enfoque completo porque estás viendo una implementación deficiente de él. Todo lo que no está documentado/difícil de depurar es malo, pero es la implementación mala. –