Deseja threads de java que não consomem memória como se não estivessem em si mesmas e não diminuam a velocidade? Bom crédito, e esse problema responde a essa pergunta.
Explicamos o trabalho do Projeto Loom em caixas de pizza! Vamos lá!
Tudo isso é removido e escrito especificamente para Habr .
Indicativos de chamada
Você costuma ver esta imagem no seu serviço da Web: no início, tudo estava bem, então um milhão de chineses veio até você, o serviço fez um milhão de threads e se afogou no inferno?

Você quer uma imagem tão bonita?

Em outras palavras, você deseja em threads de java que não consomem memória, mas não estão em si mesmos e não diminuem a velocidade? Bom crédito, e esse problema responde a essa pergunta.
Na verdade, estaremos envolvidos na descompactação do novo quadro . Lembra como o Wylsacom desempacotou os iPhones? Alguns já não se lembram dos revisores antigos, mas por quê? Porque Habr é uma fortaleza convencional e os vidos pagos são, desculpe, os raios da diarréia . Neste post, trataremos exclusivamente de hardcore técnico.
Primeiro, dois minutos para os globos oculares, isenção de responsabilidade e outro lixo, o que deve ser dito. Você pode pular se estiver com preguiça.
Antes de tudo, tudo o que foi dito no vídeo são meus pensamentos pessoais, e eles não têm nada a ver com o empregador ou a gloriosa corporação Oracle, o governo mundial de lagartos e uma coisa maldita em um morteiro. Eu até escrevo isso às três horas da manhã, para que fique claro que é minha iniciativa pessoal, meu lixo pessoal. Todas as partidas são puramente aleatórias.
Mas há outro objetivo bônus. Nós falamos constantemente sobre corotinas em Kotlin. Recentemente, houve entrevistas com Roma Elizarov , o deus de Corutin, e com Pasha Finkelstein , que escreverá sobre eles. Em breve haverá uma entrevista com Andrei Breslav - que é o pai de Kotlin. E em todos os lugares o projeto Loom é mencionado de uma maneira ou de outra, porque é um análogo da corotina. E se você não sabe o que é Loom, pode ficar burro ao ler essas entrevistas. Existem caras legais, eles discutem coisas legais. E aí está você, e você não está com eles, seu idiota. Isso é muito estúpido.
Não faça isso, leia o que é Loom neste artigo ou assista a este vídeo, explicarei tudo.
Então, qual é a complicação. Existe um cara assim, Ron Presler.

No ano passado, ele foi para a lista de discussão , disse que os threads em Java são uma porcaria e sugeriu que ele executasse o tempo de execução e o corrigisse. E todo mundo ria dele e atirava pedras, merda, se não pelo fato de ele ter escrito Quasar anteriormente, e isso é realmente muito legal. Você pode jurar no Quasar por um longo tempo, mas parece estar lá e funciona, e, no quadro geral de tudo isso, é mais provável que seja uma conquista.
Há um monte de govnokodery que não fazem nada, apenas diga. Bem, acerte, eu sou o mesmo. Ou há pessoas que parecem ser engenheiros legais, mas em geral no inconsciente, eles dizem o seguinte: "Em Java, você precisa melhorar os threads". O que melhorar? O que são tópicos?
As pessoas geralmente têm preguiça de pensar.
Como em uma piada:
Petka e Vasily Ivanovich voam em um avião.
Vasily Ivanovich pergunta: - Petka, dispositivos?
Petka responde: - 200!
Vasily Ivanovich: - E quanto a 200?
Petka: - E os eletrodomésticos?
Vou contar uma história. Eu estava na Ucrânia nesta primavera, voamos da Bielorrússia (você entende por que é impossível diretamente de São Petersburgo). E na alfândega, sentamos por cerca de duas horas, nada menos. Os funcionários da alfândega são muito gentis, perguntando com seriedade se Java é uma tecnologia obsoleta. Pessoas sentadas nas proximidades que voam para o mesmo lugar. E eu sou um tipo de orador, tenho que insistir, ficar em pé e, como esperado, falar descaradamente sobre coisas que não uso. E ao longo do caminho, ele falou sobre a distribuição JDK chamada Liberica, esse é um JDK para o Raspberry Pi.
E o que você acha? Nem mesmo seis meses se passaram antes que as pessoas batessem no meu carrinho e dissessem que, olha, nós colocamos uma solução em Liber no produto, e eu já tenho um relatório sobre isso no jfuture.by konf da Bielorrússia. Essa é a abordagem. Este não é um evangelista ruim, mas um cara, um engenheiro normal.
A propósito, em breve teremos uma conferência do Joker 2018 , que incluirá Andrei Breslav (obviamente vasculhando corotinas) e Pasha Finkelshtein e Josh Long sobre o apoio da Spring ao Loom. Bem, e um monte de especialistas proeminentes legais, vamos lá!
E agora, retornando aos threads. As pessoas tentam pensar nos seus dois neurônios degradados, como um nó sinuoso no punho e murmuram: "Em Java, os threads não são assim, em Java, os threads não são assim". Devices! Quais aparelhos? Isso geralmente é um inferno.
E aí vem Presler, um cara normal, não degradado, e a princípio ele faz uma descrição sensata. Um ano depois, vendo uma demonstração de trabalho. Eu disse tudo isso para que você entenda que uma descrição normal dos problemas, a documentação normal é um tipo especial de heroísmo. E a demonstração é geralmente espaço. Esta é a primeira pessoa que realmente fez alguma coisa nessa direção. Ele precisa mais.
Juntamente com a demo, Presler falou na conferência e lançou este vídeo:
De fato, este artigo inteiro é uma revisão do que foi dito lá. Eu não finjo absolutamente a singularidade desse material, tudo o que está neste artigo foi inventado por Ron.
A discussão é sobre três tópicos dolorosos:
- Continuação
- Fibras
- Chamadas de cauda
Provavelmente, ele estava tão enojado vendo Quasar e lutando com suas falhas que não há força - você precisa colocá-la em tempo de execução.
Foi há um ano e desde então eles têm visto um protótipo. Alguns já perderam a esperança de que algum dia veremos uma demonstração, mas há um mês eles deram à luz e mostraram o que é visível neste tweet .

Todos os três tópicos dolorosos nesta demonstração estão diretamente no código ou pelo menos moralmente presentes. Bem, sim, eles ainda não dominaram os chamados finais, mas querem.
Edição
Usuários insatisfeitos, os desenvolvedores de aplicativos, quando criam uma API, são forçados a escolher entre duas cadeiras. Os picos são construídos em uma cadeira, as flores crescem na outra. E nem um nem o outro nos convém.

Por exemplo, se você escreve um serviço que funciona de forma síncrona, funciona muito bem com o código herdado, é fácil depurar e monitorar o desempenho. Problemas surgirão com largura de banda e escalabilidade. Só porque o número de threads que agora você pode executar em um simples pedaço de hardware, em hardware comum - bem, digamos, dois mil. Isso é muito menor que o número de conexões que podem ser abertas para este servidor. Que, do ponto de vista do código de rede, pode ser quase infinito.
(Bem, sim, isso tem algo a ver com o fato de que os soquetes em Java são arranjados idiotas, mas esse é um tópico para outra conversa)
Imagine que você está escrevendo algum tipo de MMO.

Por exemplo, durante a Guerra do Norte no EVE Online, dois mil e quatrocentos pilotos se reuniram em um ponto no espaço, cada um deles - condicionalmente, se fosse escrito em Java - não seria um segmento, mas vários. E o piloto, é claro, é uma lógica de negócios complexa, e não a emissão de qualquer HTML que possa ser descartado manualmente em uma lupa.
O tempo de resposta naquela batalha foi tão longo que o jogador teve que esperar alguns minutos para aguardar o tiro. Até onde eu sei, o CCP especificamente para essa batalha lançou os enormes recursos de hardware de seu cluster.
Embora eu provavelmente cite o EVE como um exemplo em vão, porque, até onde eu entendi, tudo está escrito em Python, e em Python com multithreading ainda é pior que o nosso - e podemos considerar uma fraca competição de recursos da linguagem. Mas então o exemplo é claro e com imagens.
Se você está interessado no assunto da OMI em geral e na história da “Guerra do Norte” em particular, recentemente um vídeo muito bom sobre esse assunto apareceu no canal Bulzhat (seja lá o que esse nome significa), assista no meu registro de data e hora.
Voltamos ao tópico.
Por outro lado, você pode usar algum tipo de estrutura assíncrona. É escalável. Mas cairemos imediatamente em uma depuração muito difícil, um perfil complicado do desempenho, não poderemos integrá-lo perfeitamente ao legado, você precisará reescrever muitas coisas, envolvê-las em invólucros abomináveis e, geralmente, parecer que acabamos de ser estuprados. Várias vezes seguidas. De fato, durante dias, durante todo o tempo em que escrevermos isso, você terá que se sentir assim.
Perguntei ao especialista, o famoso acadêmico Escobar, o que ele pensa sobre isso:

O que fazer? As chamadas fibras estão com pressa para ajudar.
No caso geral, as fibras são threads tão leves que também vasculham o espaço de endereço (porque milagres não acontecem, você entende). Mas, diferentemente dos encadeamentos comuns, eles não usam multitarefa preemptiva, mas multitarefa cooperativa. Leia mais na Wikipedia .
A fibra pode aproveitar as vantagens da programação síncrona e assíncrona. Como resultado, a utilização de ferro é aumentada e usamos menos servidores no cluster para a mesma tarefa. Bem, no nosso bolso para isso, temos lavanda. Babos. Lave. Dinheiro Bem, você entendeu. Para o servidor salvo.
Na encruzilhada
A primeira coisa que quero discutir. As pessoas não entendem a diferença entre continuações e fibra.
Agora haverá uma iluminação de culto!
Anunciaremos o fato: Continuação e Fibra são duas coisas diferentes.
Continuações
As fibras são construídas em cima de um mecânico chamado Continuações.
Continuações (mais precisamente, continuações delimitadas) é um certo cálculo, execução, parte de um programa que pode adormecer, depois acorda e continua a execução do local em que adormeceu. Às vezes, pode até ser clonado ou serializado, mesmo enquanto ele está dormindo.
Usarei a palavra "continuação", e não "continuação" (como está escrita na Wikipedia), porque todos nós nos comunicamos no idioma inglês . Usando a terminologia russa normal, pode-se facilmente chegar a uma situação em que a diferença entre o termo russo e inglês se torna muito grande e ninguém mais entende o significado do que foi dito.
Às vezes, também usarei a palavra "crowding out" em vez da versão em inglês de "yield". Apenas a palavra "rendimento" - é algum tipo de desagradável. Portanto, haverá “lotação”.
Então aqui. É muito importante que não haja concorrência dentro do continuum. É, por si só, a primitiva mínima desse processo.
Você pode pensar na continuação como um Runnable
, dentro do qual você pode chamar o método pause()
. É interna e diretamente, porque nossa multitarefa é cooperativa. E então você pode executá-lo novamente e, em vez de recalcular tudo, ele continuará de onde parou. Esse tipo de mágica. Voltaremos à magia.
Onde obter uma demonstração com continuações de trabalho - discutiremos no final. Agora vamos falar sobre o que está lá.
A própria classe de continuação está localizada em java.base, todos os links estarão na descrição. ( src/java.base/share/classes/java/lang/Continuation.java
). Mas essa classe é muito grande, volumosa, por isso faz sentido olhar apenas para algum tipo de pressão dela.
public class Continuation implements Runnable { public Continuation(ContinuationScope scope, Runnable body); public final void run(); public static void yield(ContinuationScope scope); public boolean isDone(); protected void onPinned(Reason reason) { throw new IllegalStateException("Pinned: " + reason); } }
Observe que, na verdade, esse arquivo está mudando constantemente. Por exemplo, no dia anterior, a continuação não implementou a interface Runnable
. Trate isso como uma espécie de esboço.
Dê uma olhada no construtor. body
- este é o código que você está tentando executar e o scope
- é um tipo de skop que permite aninhar continuações em continuações.
Portanto, você pode pré-agendar esse código até o final com o método run
ou substituí-lo por uma matriz específica usando o método yield
(a matriz é necessária aqui para algo como ações de encaminhamento a manipuladores aninhados, mas não nos importamos como usuários). Você pode perguntar usando o método isDone
se tudo estiver concluído até o fim.
E por motivos ditados apenas pelas necessidades da implementação atual (mas, provavelmente, ela também será incluída na versão), nem sempre é possível obter yield
. Por exemplo, se dentro da continuação tivemos uma transição para o código nativo e um quadro nativo apareceu na pilha, é impossível ficar preso. Isso também acontecerá se você tentar se espremer enquanto um monitor nativo, como um método sincronizado, é levado para dentro do corpo do continuum. Por padrão, quando você tenta fingir isso, uma exceção é lançada ... mas as fibras construídas sobre as continuações sobrecarregam esse método e fazem outra coisa. Isso será um pouco mais tarde.
Você pode usar isso aproximadamente da seguinte maneira:
Continuation cont = new Continuation(SCOPE, () -> { while (true) { System.out.println("before"); Continuation.yield(SCOPE); System.out.println("after"); } }); while (!cont.isDone()) { cont.run(); }
Este é um exemplo da apresentação de Presler. Novamente, este não é um código "trivial", é algum tipo de esboço.
Este é um esboço do que estamos fazendo continuação, no meio dessa continuação, estamos lotados e, em um ciclo interminável, perguntamos se a continuação funcionou até o fim e se deve continuar.
Mas, em geral, não se pretende que programadores de aplicativos comuns se relacionem com essa API. Destina-se aos criadores de estruturas de sistema. Estruturas formadoras de sistemas como o Spring Framework adotam imediatamente esse recurso assim que ele sai. Você verá. Considere isso uma previsão. Uma previsão tão leve, porque tudo é bastante óbvio aqui. Todos os dados para previsão são. Esse recurso é importante demais para não se adaptar. Portanto, não há necessidade de se preocupar com antecedência que alguém o torture com a codificação neste formulário. Bem, se você é desenvolvedor de Spring, sabia o que estava fazendo.
E agora, além das continuações, as fibras são construídas.
Fibras
Então, o que no nosso caso significa fibra.
Este é um tipo de abstração, que é:
- Encadeamentos leves processados na própria JVM e não no sistema operacional;
- Com custos indiretos extremamente baixos para criar, manter a vida, alternar tarefas;
- Que pode ser executado milhões de vezes.
Muitas tecnologias estão tentando fabricar fibra de uma maneira ou de outra. Por exemplo, no Kotlin, existem corotinas implementadas na geração muito inteligente de bytecodes. MUITO INTELIGENTE . Mas o tempo de execução é um lugar melhor para implementar essas coisas.
No mínimo, a JVM já sabe lidar bem com os threads, e tudo o que precisamos fazer é otimizar o processo de codificação para multithreading. Você pode usar APIs assíncronas, mas isso dificilmente pode ser chamado de "simplificação": mesmo usando coisas como o Reactor , o Spring Project Reactor, que permite escrever código aparentemente linear, não ajudará muito se você precisar depurar problemas complexos.
So Fiber.
Fibra consiste em dois componentes. Isto é:
Isto é:

Você pode decidir quem é o planejador aqui. Eu acho que o planejador aqui é Jay.
- A fibra envolve o código que você deseja executar em uma continuação
- O Agendador os lança em um pool de threads de operadora
Vou chamá-los de segmentos de operadora.

O protótipo atual usa java.util.concurrent.Executor
e o planejador ForkJoinPool
. Nós temos tudo. No futuro, algo mais inteligente pode aparecer lá, mas por enquanto, assim.
Como a continuação se comporta:
- Está lotado (rendimento) quando ocorre um bloqueio (por exemplo, no IO);
- Continua quando estiver pronto para continuar (por exemplo, a operação de E / S foi concluída e você pode prosseguir).
Status atual das obras:
- O foco principal na filosofia, conceitos;
- A API não é fixa, é "para exibição". Este é um protótipo de pesquisa;
- Existe um protótipo de trabalho codificado pronto para a classe
java.lang.Fiber
.
Será discutido.
O que já foi serrado na fibra:
- Executa um lançamento de tarefa;
- Estacionamento sem estacionamento em uma transportadora;
- Aguardando a conclusão da fibra.
Diagrama de circuito
mount(); try { cont.run(); } finally () { unmount(); }
- Podemos montar uma fibra em um suporte de linha;
- Em seguida, execute a continuação;
- E espere até que ela esteja lotada ou pare honestamente;
- No final, sempre deixamos o tópico.
Esse pseudocódigo será executado no ForkJoinPool
ou em algum outro (que eventualmente estará na versão final).
Use na realidade
Fiber f = Fiber.execute( () -> { System.out.println("Good Morning!"); readLock.lock(); try { System.out.println("Good Afternoon"); } finally { readLock.unlock(); } System.out.println("Good Night"); });
Olha, estamos criando uma fibra na qual:
- bem vindo a todos;
- estamos bloqueando o loke reentrante;
- ao retornar, parabéns pelo seu almoço;
- finalmente solte a trava;
- e diga adeus.
Tudo é muito simples.
Não causamos aglomeração diretamente. O próprio Project Loom sabe que quando readLock.lock();
acionado readLock.lock();
ele deve intervir e implicitamente fazer repressão. O usuário não vê isso, mas acontece lá.
Pilhas, pilhas em todos os lugares!
Vamos demonstrar o que está acontecendo usando a pilha de pizza como exemplo.
No início, o encadeamento do transportador está em um estado de espera e nada acontece.

Topo da pilha no topo, lembre-se.
Em seguida, a fibra foi agendada para execução e a tarefa de fibra começou a ser executada.

Dentro de si, ele obviamente lança uma continuação, na qual o código real já está localizado.

Do ponto de vista do usuário, ainda não lançamos nada aqui.
Esse é apenas o primeiro quadro do código do usuário que aparece na pilha e está marcado em roxo.
Além disso, o código é executado, executado, em algum momento a tarefa está tentando capturar o bloqueio e bloquear nele, o que leva à exclusão automática.

Tudo o que está na pilha de continuação é armazenado em um determinado lugar mágico. E desaparece.

Como você pode ver, o fluxo retorna para a fibra, para a instrução que segue Continuation.run
. E este é o fim do código da fibra.
A tarefa de fibra termina, o suporte de mídia está aguardando um novo trabalho.

A fibra está estacionada, em algum lugar fica, o continuum está completamente lotado.
Mais cedo ou mais tarde, chega o momento em que quem é dono da fechadura a libera.
Isso leva ao fato de que a fibra, que estava aguardando a liberação da trava, é desembalada. A tarefa dessa fibra começa novamente.
- Reentrantlock.unlock
- Locksupport.unpark
- Fiber.unpark
- ForkJoinPool.execute
E rapidamente retornamos à pilha, que foi recentemente.

Além disso, o fio transportador pode ser completamente diferente. E isso faz sentido!
Execute a continuação novamente.

E aí vem a MAGIA !!! A pilha é restaurada e a execução continua com a instrução após Continuation.yield
.

Saímos da trava recém estacionada e começamos a executar todo o código restante na continuação:

A tarefa do usuário termina e o controle retorna à tarefa de fibra imediatamente após a instrução continuation.run

Ao mesmo tempo, a execução da fibra termina e novamente nos encontramos no modo de espera.

O próximo lançamento da fibra inicia novamente todo o ciclo de renascimentos descrito acima.

Exemplos ao vivo
E quem disse que tudo isso funciona? Trata-se de algumas marcas de micropigmentação escritas durante a noite?
Como exemplo da operação dos fogos de artifício, os oraklovitas escreveram um pequeno servidor web e o alimentaram com pedidos para que ele se engasgasse. Então eles foram transferidos para fibra. O servidor parou de engasgar e, a partir disso, concluímos que as fibras funcionam.
Não tenho o código exato para este servidor, mas se esta postagem receber curtidas e comentários suficientes, tentarei escrever um exemplo e criar gráficos reais.
Os problemas
Há algum problema aqui? Sim claro! Toda a história com faybers é uma história sobre problemas e trocas contínuas.
Problemas filosóficos
- Precisamos reinventar threads?
- Todo o código existente deve funcionar corretamente dentro da fibra?
O protótipo atual é executado com limitações. O que pode ser lançado, embora eu não queira. Ainda assim, o OpenJDK é uma coisa que respeita a compatibilidade sem fim.
Quais são as limitações técnicas? As limitações mais óbvias são 2 peças.
O problema é uma vez - você não pode substituir os quadros nativos
PrivilegedAction<Void> pa = () -> { readLock.lock();
Aqui doPrivileged chama o método nativo.
Você chama doPrivileged
, sai da VM, um quadro nativo aparece na sua pilha e depois tenta estacionar na linha readLock.lock()
. E nesse momento, o fio transportador ficará manchado até que seja retirado. Ou seja, o segmento desaparece. Nesse caso, os fios transportadores podem terminar e, em geral, isso quebra toda a ideia de fibra.
A maneira de resolver isso já é conhecida, e as discussões estão em andamento sobre isso.
Problema dois - blocos sincronizados
Isso é lixo muito mais sério
synchronized (object) {
synchronized (object) {
No caso de captura do monitor na fibra, os fios transportadores também chutam.
É claro que em um código completamente novo você pode alterar os monitores para bloqueios diretos, em vez de esperar + notificar que você pode usar objetos de condição, mas o que fazer com o legado? Isso é um problema.
API de thread? Thread.currentThread ()? Thread locals?
No protótipo atual, Thread
e Fiber
criaram uma superclasse comum chamada Strand
.
Isso permite que você transfira a API da maneira mais mínima possível.
O que fazer a seguir - como sempre neste projeto, é uma pergunta.
O que está acontecendo com a API Thread agora?
- O primeiro uso de
Thread.currentThread()
em uma fibra cria um tipo de thread de sombra, Shadow Thread; - do ponto de vista do sistema, esse é um encadeamento "não lançado" e não há metainformações de VM nele;
- ST tenta emular tudo o que pode;
- mas você precisa entender que a API antiga tem muito lixo;
- mais especificamente, o Shadow Thread implementa a API do Thread para tudo, exceto
stop
, suspend
, resume
e manipular exceções não capturadas.
O que fazer com os locais de threads?
- agora os locais de thread apenas se transformam em locais de fibra;
- existem muitos problemas com isso, tudo isso está sendo discutido;
- um conjunto de usos é especialmente discutido;
- Historicamente, os threads têm sido usados correta e incorretamente (aqueles que usam incorretamente ainda esperam algo, e você não pode decepcioná-los completamente);
- em geral, isso cria uma gama completa de aplicativos:
- Alto nível: cache de conexões ou senhas no contêiner;
- Nível baixo: processador nas bibliotecas do sistema.
Quanto tudo isso come

Tópico:
- Pilha: 1 MB e 16 KB em estruturas de dados do kernel;
- Por instância do encadeamento: 2300 bytes, incluindo meta informações da VM.
Fibra:
- Pilha de continuação: de centenas de bytes a kilobytes;
- Por instância de fibra: 200-240 bytes.
A diferença é enorme!
E é exatamente isso que permite que milhões de pessoas disparem.
O que pode estacionar
É claro que o mais mágico é o estacionamento automático quando ocorrem alguns eventos. O que é suportado atualmente?
- Thread.sleep, join;
- java.util.concurrent e LockSupport.lock;
- IO: rede em soquetes (leitura, gravação, conexão, aceitação), arquivos, tubos;
- Tudo isso está inacabado, mas a luz no túnel é visível.
Comunicação entre fibra
Outra pergunta que todo mundo faz é: como trocar informações competitivas entre fibras.
- O protótipo atual inicia tarefas em
Runnable
, pode ser convertido em CompletableFuture
, se por algum motivo você precisar; - java.util.concurrent "simplesmente funciona". Você pode atrapalhar tudo de uma maneira padrão;
- pode haver novas APIs para multithreading, mas isso não é exato;
- um monte de pequenas perguntas como "as fibras devem retornar valores?"; tudo é discutido, eles não estão no protótipo.
Como as continuações são implementadas no protótipo?
Requisitos óbvios são impostos à continuação: você precisa usar o mínimo de RAM possível e alternar entre eles o mais rápido possível. Caso contrário, não funcionará para mantê-los na casa dos milhões. A principal tarefa aqui é, de alguma forma, não fazer uma cópia completa da pilha para cada parque de estacionamento. E existe esse esquema! Vamos tentar explicar isso nas fotos.
A maneira mais legal seria, é claro, colocar todas as pilhas em um quadril java e usá-las diretamente. Mas não está claro como codificar agora, então o protótipo usa a cópia. Mas copiar com um pequeno mas importante hack.
Temos duas cadeiras ... quero dizer, duas pilhas. Duas matrizes de java no quadril. Uma é uma matriz de objetos, onde armazenaremos referências a objetos. O segundo é primitivo (por exemplo, íntimo), que manipulará todo o resto.

Agora estamos em um estado em que a continuação está prestes a ser realizada pela primeira vez.
run
chama um método interno chamado enter
:

E então o código do usuário é executado, até a primeira exclusão.

Nesse ponto, é feita uma chamada de VM, que freeze
. Neste protótipo, isso é feito diretamente fisicamente - usando cópia.

Iniciamos o processo de copiar seqüencialmente os quadros da pilha nativa para o java hip.

É necessário verificar se os monitores estão lá ou se o código nativo é usado, ou algo mais que realmente não nos permita continuar trabalhando.

E se tudo estiver bem, copiamos primeiro em uma matriz primitiva:

Em seguida, isolamos as referências aos objetos e as salvamos na matriz de objetos:

Na verdade, dois chás para todos que leram para este lugar!
Além disso, continuamos este procedimento para todos os outros elementos da pilha nativa.

Viva! Copiamos tudo para o ninho no quadril. Você pode pular com segurança para o local de chamada sem medo de que tenhamos perdido alguma coisa. Tudo é moderno.

Agora, mais cedo ou mais tarde, o código de chamada chamará nossa continuação novamente. E ela deve continuar do lugar onde foi deixada na última vez. Esta é a nossa tarefa.

Verificando se a continuação estava em execução, diz que sim, estava em execução. Portanto, você precisa chamar a VM, limpar um pouco de espaço na pilha e chamar a função interna da thaw
. "Thaw" é traduzido para o russo como "thaw", "descongelar", o que parece bastante lógico. É necessário descongelar os quadros da pilha de continuação em nossa pilha nativa principal.
Não tenho certeza de que o degelo do chá seja bem claro. Abstração ruim é como um gatinho com uma porta. Mas isso fará por nós.

Nós fazemos cópias bastante óbvias.
Primeiro com uma matriz primitiva:

Em seguida, no link:

Você precisa corrigir um pouco as cópias para obter a pilha correta:

Repita a obscenidade para todos os quadros:

Agora você pode voltar a yield
e continuar como se nada tivesse acontecido.

O problema é que uma cópia completa da pilha não é exatamente o que gostaríamos de ter. É muito inibitório. Tudo isso é isolamento de links, verificações para fixação, não é rápido. E o mais importante - tudo isso depende linearmente do tamanho da pilha! Em uma palavra, inferno. Não há necessidade de fazer isso.
Em vez disso, temos outra ideia - cópia preguiçosa.
Vamos voltar ao local onde já temos uma continuação congelada.

Continuamos o processo como antes:

Da mesma maneira que antes, limpamos o local na pilha nativa:

Mas não estamos copiando tudo em sequência, mas apenas um ou alguns quadros:

Agora o hack. Você precisa corrigir o endereço de retorno do método C
para que ele aponte para uma certa barreira de retorno:

Agora você pode retornar com segurança para yield
:

O que, por sua vez, levará a uma chamada para o código do usuário no método C
:

Agora imagine que C
quer retornar ao código que o chamou. Mas seu invocador é B
, e ele não está na pilha! Portanto, quando ele tenta retornar, ele irá para o endereço de retorno, e esse endereço agora é a barreira do retorno. E, você sabe, isso atrairá novamente um thaw
:

E o thaw
descongelará o próximo quadro na pilha de continuação, e este é B
:

De fato, nós o copiamos preguiçosamente, mediante solicitação.
Em seguida, soltamos B
da pilha de continuação e configuramos a barreira novamente (a barreira precisa ser configurada porque resta algo na pilha de continuação). E assim por diante.

Mas suponha que B
não retorne ao código de chamada, mas primeiro chame outro método D
E esse novo método também quer ser excluído.

Nesse caso, quando chegar a hora de freeze
, precisaremos copiar apenas a parte superior da pilha nativa na pilha de continuação:

Assim, a quantidade de trabalho realizado não depende linearmente do tamanho da pilha. Linearmente, depende apenas do número de quadros que realmente usamos no trabalho.
O que resta?
Os desenvolvedores mantêm alguns recursos em mente, mas não entraram no protótipo.
- Serialização e clonagem. A capacidade de continuar em outra máquina, em outro momento, etc.
- JVM TI e depuração, como se fossem encadeamentos regulares. Se você estiver bloqueado ao ler o soquete, não verá um belo salto no rendimento, no protótipo o encadeamento será simplesmente bloqueado, como qualquer outro encadeamento comum.
- A recursão da cauda nem foi tocada.
Próximas etapas:
- Faça uma API humana;
- Adicione todos os recursos ausentes;
- Melhore o desempenho.
Onde obter
O protótipo é feito como um brunch no repositório OpenJDK. Você pode fazer o download do protótipo aqui , alternando para as fibers
brunch.
É feito assim:
$ hg clone http://hg.openjdk.java.net/loom/loom $ cd loom $ hg update -r fibers $ sh configure $ make images
Como você sabe, tudo isso iniciará a montagem de todo o maldito OpenJDK. Portanto, primeiro, a próxima meia hora de sua vida terá que fazer outra coisa, enquanto tudo isso acontecerá.

Em segundo lugar, você precisa ter um computador configurado corretamente com uma cadeia de ferramentas C ++ e bibliotecas GNU. Estou sugerindo que não é recomendável fazer isso no Windows. Sério, mesmo com o download do VirtualBox e a instalação de um novo Ubuntu, você gastará muito menos tempo do que tentar perceber outro erro desumano ao criar a partir do Cygwin ou do msys64. É aqui que o msys é ainda pior que o Cygwin.
Embora tudo isso seja mentira, é claro, eu me cansei de escrever as instruções de montagem .
- , mercurial extension fsmonitor. , , hg help -e fsmonitor
.
~/.hgrc :
[fsmonitor] mode = on
- . -, cp -R ./loom ./loom-backup
.
, . , Java- , .
sh configure
- . , Ubuntu, Autoconf ( sudo apt-get install autoconf
). — OpenJDK Ubuntu, , . Windows , .
, , hg diff --stat -r default:fibers
.
, , , .
Conclusão
«, ». «», . «Loom» — « ». Project Loom .
, . , «» , , — , , , — .
, , XIX , .

. -, .
, . IDE .
, , , « », «», « » .
? . .
Obrigada