Um uso importante de co-rotinas é a provisão de um ambiente de multitasking, onde diversas "tarefas" (linhas de execução) podem executar simultaneamente, compartilhando o uso do processador. Diferentemente de um ambiente baseado em threads, que é tipicamente preemptivo (i.e., as tarefas perdem involuntariamente o controle do processador, em qualquer momento de sua execução), um ambiente baseado em co-rotinas é basicamente cooperativo, ou seja, as tarefas cedem voluntariamente o controle do processador.
O controle de execução de tarefas em um ambiente de multitasking é tipicamente realizado através de uma estrutura chamada de escalonador de tarefas: ele é o responsável por iniciar (ou retomar) cada uma das tarefas "ativas", recebendo o controle de volta quando essa tarefa ceder o controle.
Um escalonador de tarefas utiliza basicamente uma fila (ou lista) de tarefas ativas. Ele implementa um "loop" que retira uma tarefa da fila, inicia/retoma a execução dessa tarefa e, ao receber o controle de volta (quando a tarefa ceder o controle), verifica se a tarefa terminou. Se ela não terminou, ele a reinsere na fila. Um "esqueleto" para o escalonador poderia ser
void dispatcher() { while (1) { /****************/ /* obtem a proxima tarefa da fila: se fila vazia, termina! */ /****************/ /* retoma a tarefa */ /* Se terminou, destroi a tarefa */ /* Se não terminou re-insere na fila */ } printf ("acabaram todas as tarefas \n"); }
Um escalonador pode adotar diferentes políticas para escolher a próxima tarefa a executar. A política mais simples é o que chamamos de round-robin: a próxima tarefa é a primeira na fila; quando ela cede o controle, volta para o fim da fila (um escalonamento para tarefas de prioridades diferentes poderia usar uma fila com prioridades...).
Note que cada tarefa é descrita por uma estrutura que contém um identificador,
um ponteiro para próxima tarefa e um ponteiro void
, que será utilizado
para armazenar a co-rotina associada à tarefa.
main
,
que inicializa a fila de tarefas, cria algumas co-rotinas cujo corpo é a função
boba
, e insere tarefas associadas a essas co-rotinas na fila de tarefas.
Em seguida, ela ativa (chama) o escalonador (dispatcher
).
boba
, que recebe um único parâmetro,
um int
, que irá representar o número da tarefa
(o escalonador obtém esse número no campo id
do descritor da tarefa,
e o repassa à co-rotina como o segundo parâmetro da chamada a coro_resume
).
boba
deve conter um loop com chamadas à função mostra
,
que recebe dois inteiros, o número da tarefa e o valor de um contador (uma variável
local de boba
, como fizemos em ping
e pong
,
no laboratório passado).
Note que quem faz o yield
das tarefas é a função mostra
,
que sempre devolve o valor 0, para indicar que a tarefa ainda está ativa.
A função boba
, ao terminar, deve retornar o valor 1, para o dispatcher
saber que a tarefa terminou.
dispatcher
, que
executa, em round-robin as tarefas inseridas na fila pela main
.
gcc -m32 -Wa,--execstack -o meuprog core.s corotinas.c task.c multitask.ce o execute!
boba
criar e inserir na fila uma nova tarefa
(você pode criar um "corpo" diferente para ela...)