3.4 Concorrência: Tempo é da Essência
Vimos o poder de objetos computacionais com estado local como ferramentas para modelagem. No entanto, como a seção 3.1.3 alertou, esse poder exige um preço: a perda da transparência referencial, dando origem a um emaranhado de questões sobre identidade e mudança, e a necessidade de abandonar o modelo de substituição de avaliação em favor do modelo de ambiente mais intricado.
A questão central que se esconde sob a complexidade do estado, identidade e mudança é que, ao introduzir a atribuição, somos forçados a admitir o tempo em nossos modelos computacionais. Antes de introduzirmos a atribuição, todos os nossos programas eram atemporais, no sentido de que qualquer expressão que possui um valor sempre possui o mesmo valor. Em contraste, relembre o exemplo de modelar saques de uma conta bancária e retornar o saldo resultante, introduzido no início da seção 3.1.1:
withdraw(25);
75
withdraw(25);
50
Aqui, avaliações sucessivas da mesma expressão produzem valores diferentes. Esse comportamento surge do fato de que a execução de atribuições (neste caso, atribuições à variável balance) delineia momentos no tempo quando valores mudam. O resultado da avaliação de uma expressão depende não apenas da expressão em si, mas também se a avaliação ocorre antes ou depois desses momentos. Construir modelos em termos de objetos computacionais com estado local nos força a confrontar o tempo como um conceito essencial na programação.
Podemos ir mais longe na estruturação de modelos computacionais para corresponder à nossa percepção do mundo físico. Os objetos no mundo não mudam um de cada vez em sequência. Ao invés disso, os percebemos como agindo concorrentemente—todos ao mesmo tempo. Assim, é frequentemente natural modelar sistemas como coleções de threads (sequências de etapas computacionais)1 que executam concorrentemente. Assim como podemos tornar nossos programas modulares organizando modelos em termos de objetos com estado local separado, frequentemente é apropriado dividir modelos computacionais em partes que evoluem separadamente e concorrentemente. Mesmo que os programas sejam executados em um computador sequencial, a prática de escrever programas como se fossem executados concorrentemente força o programador a evitar restrições de temporização inessenciais e assim torna os programas mais modulares.
Além de tornar os programas mais modulares, a computação concorrente pode fornecer uma vantagem de velocidade sobre a computação sequencial. Computadores sequenciais executam apenas uma operação por vez, então a quantidade de tempo necessária para executar uma tarefa é proporcional ao número total de operações executadas.2 No entanto, se for possível decompor um problema em partes que são relativamente independentes e precisam se comunicar apenas raramente, pode ser possível alocar partes a processadores de computação separados, produzindo uma vantagem de velocidade proporcional ao número de processadores disponíveis.
Infelizmente, as complexidades introduzidas pela atribuição tornam-se ainda mais problemáticas na presença de concorrência. O fato da execução concorrente, seja porque o mundo opera em paralelo ou porque nossos computadores operam, implica complexidade adicional em nosso entendimento do tempo.