Por que as fibras não podem utilizar vários processadores?

8

Parece que a distinção entre fibras e fios é que as fibras são programadas cooperativamente, enquanto que as threads são programadas preventivamente. O objetivo do planejador parece ser uma maneira de fazer com que um recurso serial do processador atue de maneira paralela, "compartilhando o tempo" da CPU. No entanto, em um processador de núcleo duplo com cada núcleo executando seu próprio encadeamento, presumo que não seja necessário pausar a execução de um encadeamento para que o outro continue, porque eles não estão "compartilhando o tempo" de um único processador.

Portanto, se a diferença entre threads e fibras é a maneira como elas são interrompidas pelo agendador e a interrupção não é necessária ao executar em núcleos fisicamente separados, por que as fibras não podem tirar proveito de vários núcleos de processador quando os threads podem?

Fontes de confusão:

..principalmente wikipedia

  1. http://en.wikipedia.org/wiki/Fiber_%28computer_science%29

    Uma desvantagem é que as fibras não podem utilizar máquinas multiprocessadoras sem usar também threads preventivos

  2. http://en.wikipedia.org/wiki/Computer_multitasking#Multithreading

    ... [fibras] tendem a perder alguns ou todos os benefícios de threads em máquinas com vários processadores.

James M. Lay
fonte

Respostas:

9

A principal distinção, como você aponta na sua pergunta, é se o agendador deve ou não antecipar um encadeamento. A maneira como um programador pensa sobre o compartilhamento de estruturas de dados ou sobre a sincronização entre "threads" é muito diferente nos sistemas preventivo e cooperativo.

Em um sistema de cooperativa (que vai por muitos nomes, cooperação multi-tasking , nonpreemptive multi-tasking , threads de usuário , fios verdes e fibras estão cinco dos mais comuns atualmente) o programador é garantido que seu código será executado atomicamente enquanto eles não fazem nenhuma chamada ou ligação ao sistema yield(). Isso torna particularmente fácil lidar com estruturas de dados compartilhadas entre várias fibras. A menos que você precise fazer uma chamada do sistema como parte de uma seção crítica, as seções críticas não precisam ser marcadas (com mutex locke unlockchamadas, por exemplo). Então, em código como:

x = x + y
y = 2 * x

o programador não precisa se preocupar com o fato de que alguma outra fibra possa estar trabalhando com as variáveis xe yao mesmo tempo. xe yserão atualizados juntos atomicamente da perspectiva de todas as outras fibras. Da mesma forma, todas as fibras poderiam compartilhar uma estrutura mais complicada, como uma árvore e uma chamada como tree.insert(key, value)não precisariam ser protegidas por nenhum mutex ou seção crítica.

Por outro lado, em um sistema multithreading preventivo, como em threads verdadeiramente paralelos / multicore, toda intercalação possível de instruções entre threads é possível, a menos que haja seções críticas explícitas. Uma interrupção e preempção pode ocorrer entre duas instruções. No exemplo acima:

 thread 0                thread 1
                         < thread 1 could read or modify x or y at this point
 read x
                         < thread 1 could read or modify x or y at this point
 read y
                         < thread 1 could read or modify x or y at this point
 add x and y
                         < thread 1 could read or modify x or y at this point
 write the result back into x
                         < thread 1 could read or modify x or y at this point
 read x
                         < thread 1 could read or modify x or y at this point
 multiply by 2
                         < thread 1 could read or modify x or y at this point
 write the result back into y
                         < thread 1 could read or modify x or y at this point

Portanto, para estar correto em um sistema preventivo ou em um sistema com threads verdadeiramente paralelos, você precisa cercar cada seção crítica com algum tipo de sincronização, como um mutex lockno início e um mutex unlockno final.

Assim, as fibras são mais semelhantes às bibliotecas de E / S assíncronas do que aos encadeamentos preventivos ou verdadeiramente paralelos. O planejador de fibra é chamado e pode alternar fibras durante operações de E / S de longa latência. Isso pode oferecer o benefício de várias operações simultâneas de E / S sem exigir operações de sincronização em seções críticas. Assim, o uso de fibras pode, talvez, ter menos complexidade de programação do que encadeamentos preventivos ou verdadeiramente paralelos, mas a falta de sincronização em torno de seções críticas levaria a resultados desastrosos se você tentasse executar as fibras de forma simultânea ou preventiva.

Lógica Errante
fonte
Eu acho que alguma menção provavelmente deveria ser feita a 1. sistemas híbridos nos quais o sistema de encadeamentos no nível do usuário se encarrega de distribuir (muitos) encadeamentos no nível do usuário entre (poucos) núcleos de CPU e 2. o fato de que ao programar em "bare metal" , é possível obter multiprocessamento sem preempção.
Dfeuer
1
@ Dfeuer Eu não acho que a pergunta está pedindo todas as diferentes maneiras possíveis de tirar proveito do multiprocessamento. A pergunta que li é "por que as fibras (também conhecidas como tarefas não preemptivas) não podem ser tratadas como threads preemptivas?" Se você está assumindo um paralelismo real, precisa sincronizar corretamente, para não ter mais "fibras".
Wandering Logic
1
Bela resposta. As fibras não podem garantir a segurança porque o programa presume que ele tenha acesso exclusivo a recursos compartilhados até que ele especifique um ponto de interrupção, em que os threads supõem que um acesso / mutação possa ser feito a qualquer momento; obviamente, a suposição mais segura quando vários nós verdadeiramente paralelos estão interagindo com os mesmos dados.
James M. Lay
6

A resposta é realmente que eles poderiam, mas há um desejo de não fazê-lo.

As fibras são usadas porque permitem controlar como a programação ocorre. Portanto, é muito mais simples projetar alguns algoritmos usando fibras, porque o programador disse em que fibra está sendo executada a qualquer momento. No entanto, se você deseja que duas fibras sejam executadas em dois núcleos diferentes ao mesmo tempo, é necessário agendá-las manualmente.

Os encadeamentos controlam qual código está sendo executado no sistema operacional. Em troca, o sistema operacional cuida de muitas tarefas feias para você. Alguns algoritmos ficam mais difíceis, porque o programador tem menos a dizer em qual código é executado em um determinado momento, para que casos mais inesperados possam surgir. Ferramentas como mutex e semáforos são adicionadas a um sistema operacional para dar ao programador controle suficiente para tornar os threads úteis e reduzir parte da incerteza, sem atrapalhar o programador.

Isso leva a algo que é ainda mais importante que cooperativo x preventivo: as fibras são controladas pelo programador, enquanto os threads são controlados pelo sistema operacional.

Você não precisa gerar uma fibra em outro processador. Os comandos no nível de montagem para fazer isso são atrozmente complicados e geralmente são específicos do processador. Você não precisa escrever 15 versões diferentes do seu código para lidar com esses processadores; portanto, vire para o sistema operacional. O trabalho do sistema operacional é abstrair essas diferenças. O resultado são "threads".

As fibras passam por cima dos fios. Eles não correm por conta própria. Portanto, se você deseja executar duas fibras em núcleos diferentes, basta gerar dois threads e executar uma fibra em cada um deles. Em muitas implementações de fibras, você pode fazer isso facilmente. O suporte multicore não vem das fibras, mas dos fios.

Torna-se fácil mostrar que, a menos que você queira escrever seu próprio código específico do processador, não há nada que você possa fazer atribuindo fibras a vários núcleos que você não poderia fazer criando threads e atribuindo fibras a cada um. Uma das minhas regras favoritas para o design da API é "Uma API não é concluída quando você termina de adicionar tudo a ela, mas quando não consegue mais encontrar algo para remover". Dado que o multi-core é tratado perfeitamente hospedando fibras em threads, não há motivo para complicar a API de fibra adicionando multi-core nesse nível.

Cort Ammon
fonte