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 lock
e unlock
chamadas, 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 x
e y
ao mesmo tempo. x
e y
serã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 lock
no início e um mutex unlock
no 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.