Como se consegue a funcionalidade multithread em linguagem de alto nível, como Java, usando apenas um thread e máquina de estado? Por exemplo, e se houver duas atividades a serem executadas (cálculos e E / S) e uma atividade puder ser bloqueada?
O que você está descrevendo é chamado de multitarefa cooperativa , em que as tarefas recebem a CPU e espera-se que a abandone voluntariamente após uma quantidade determinada de tempo ou atividade. Uma tarefa que não coopera continuando a usar a CPU ou bloqueando as gengivas durante todo o trabalho e, além de ter um cronômetro de vigilância de hardware, não há nada que o código que supervisiona as tarefas possa fazer a respeito.
O que você vê nos sistemas modernos é chamado multitarefa preemptiva , que é onde as tarefas não precisam renunciar à CPU porque o supervisor faz isso por elas quando chega uma interrupção gerada por hardware. A rotina de serviço de interrupção no supervisor salva o estado da CPU e a restaura na próxima vez que a tarefa for considerada merecedora de um intervalo de tempo, depois restaura o estado de qualquer tarefa a ser executada em seguida e volta para ela como se nada tivesse acontecido . Essa ação é chamada de alternância de contexto e pode ser cara.
O uso de "somente máquina de estado" é uma alternativa viável ao multiencadeamento em idiomas de alto nível?
Viável? Certo. Sane? As vezes. O uso de threads ou de alguma forma de multitarefa cooperativa caseira (por exemplo, máquinas de estado) depende das compensações que você deseja fazer.
Os encadeamentos simplificam o design da tarefa até o ponto em que você pode tratar cada um como seu próprio programa que compartilha o espaço de dados com outras pessoas. Isso lhe dá a liberdade de se concentrar no trabalho em questão e não em todo o gerenciamento e serviço de limpeza necessário para fazer com que ele funcione uma iteração por vez. Mas como nenhuma boa ação fica impune, você paga por toda essa conveniência nas alternâncias de contexto. Ter muitos encadeamentos que produzem a CPU após realizar um trabalho mínimo (voluntariamente ou fazer algo que bloqueie, como E / S) pode consumir muito tempo do processador na alternância de contexto. Isso é especialmente verdadeiro se suas operações de bloqueio raramente bloqueiam por muito tempo.
Existem algumas situações em que a rota cooperativa faz mais sentido. Certa vez, tive que escrever algum software da terra do usuário para um pedaço de hardware que transmitisse muitos canais de dados através de uma interface mapeada na memória que exigia pesquisa. Cada canal era um objeto criado de forma que eu pudesse deixá-lo rodar como um encadeamento ou executar repetidamente um único ciclo de pesquisa.
O desempenho da versão multithread não foi bom, exatamente pelo motivo que descrevi acima: cada thread fazia um trabalho mínimo e depois produzia a CPU para que os outros canais pudessem ter algum tempo, causando muitas alternâncias de contexto. Deixar os encadeamentos livres até serem antecipados ajudou com a taxa de transferência, mas resultou em alguns canais não sendo atendidos antes que o hardware experimentasse uma saturação de buffer porque eles não obtiveram uma fatia de tempo tão cedo.
A versão single-threaded, que fazia as iterações de cada canal, corria como um macaco escaldado e a carga no sistema caía como uma rocha. A penalidade que paguei pelo desempenho adicional foi ter que fazer malabarismos com as tarefas. Nesse caso, o código para fazê-lo era simples o suficiente para que o custo de desenvolvimento e manutenção valesse a pena a melhoria de desempenho. Eu acho que essa é realmente a linha de fundo. Se meus tópicos estivessem esperando por alguma chamada do sistema, o exercício provavelmente não teria valido a pena.
Isso me leva ao comentário de Cox: threads não são exclusivamente para pessoas que não podem escrever máquinas de estado. Algumas pessoas são capazes de fazer isso, mas optam por usar uma máquina de estado enlatada (ou seja, um thread) no interesse de concluir o trabalho mais cedo ou com menos complexidade.