a partir do Rails 4 , tudo teria que rodar em um ambiente threaded por padrão. O que isso significa é que todo o código que escrevemos E TODAS as joias que usamos devem serthreadsafe
então, eu tenho algumas perguntas sobre isso:
- o que NÃO é thread-safe em ruby / rails? Vs O que é thread-safe em ruby / rails?
- Existe uma lista de jóias que é conhecido por ser threadsafe ou vice-versa?
- existe uma lista de padrões comuns de código que NÃO são exemplos threadsafe
@result ||= some_method
? - As estruturas de dados no ruby lang core, como
Hash
etc, são threadsafe? - Na ressonância magnética, onde há um
GVL
/ oGIL
que significa que apenas 1 thread ruby pode ser executado por vez, excetoIO
, a mudança de threadsafe nos afeta?
ruby
multithreading
concurrency
thread-safety
ruby-on-rails-4
CuriousMind
fonte
fonte
Respostas:
Nenhuma das estruturas de dados principais é thread-safe. O único que conheço que vem com Ruby é a implementação de fila na biblioteca padrão (
require 'thread'; q = Queue.new
).O GIL da MRI não nos salva de problemas de segurança de thread. Ele apenas garante que dois threads não possam executar o código Ruby ao mesmo tempo , ou seja, em duas CPUs diferentes ao mesmo tempo. Threads ainda podem ser pausados e retomados em qualquer ponto do código. Se você escrever código como,
@n = 0; 3.times { Thread.start { 100.times { @n += 1 } } }
por exemplo, alterar uma variável compartilhada de vários threads, o valor da variável compartilhada posteriormente não é determinístico. O GIL é mais ou menos uma simulação de um sistema de núcleo único, ele não muda as questões fundamentais de escrever programas concorrentes corretos.Mesmo que a MRI fosse de thread único como o Node.js, você ainda teria que pensar sobre a simultaneidade. O exemplo com a variável incrementada funcionaria bem, mas você ainda pode obter condições de corrida em que as coisas acontecem em ordem não determinística e um retorno de chamada supera o resultado de outro. Os sistemas assíncronos de encadeamento único são mais fáceis de raciocinar, mas não estão livres de problemas de simultaneidade. Pense em um aplicativo com vários usuários: se dois usuários clicarem em editar em uma postagem do Stack Overflow mais ou menos ao mesmo tempo, passe algum tempo editando a postagem e clique em Salvar, cujas alterações serão vistas por um terceiro usuário mais tarde, quando eles leu essa mesma postagem?
Em Ruby, como na maioria dos outros tempos de execução simultâneos, qualquer coisa que seja mais de uma operação não é thread-safe.
@n += 1
não é seguro para thread, porque é várias operações.@n = 1
é thread-safe porque é uma operação (são muitas operações por baixo do capô, e eu provavelmente teria problemas se tentasse descrever por que é "thread-safe" em detalhes, mas no final você não obterá resultados inconsistentes de atribuições )@n ||= 1
, não é e nenhuma outra operação abreviada + atribuição é. Um erro que cometi muitas vezes é escreverreturn unless @started; @started = true
, o que não é seguro para threads.Não conheço nenhuma lista autorizada de instruções thread-safe e non-thread safe para Ruby, mas existe uma regra simples: se uma expressão faz apenas uma operação (sem efeitos colaterais), provavelmente é thread-safe. Por exemplo:
a + b
está ok,a = b
também está ok, ea.foo(b)
está ok, se o métodofoo
for livre de efeitos colaterais (uma vez que quase tudo em Ruby é uma chamada de método, mesmo atribuição em muitos casos, isso vale para os outros exemplos também). Os efeitos colaterais neste contexto significam coisas que mudam de estado. nãodef foo(x); @x = x; end
é livre de efeitos colaterais.Uma das coisas mais difíceis sobre escrever código thread-safe em Ruby é que todas as estruturas de dados centrais, incluindo array, hash e string, são mutáveis. É muito fácil vazar acidentalmente uma parte do seu estado e, quando essa parte é mutável, as coisas podem ficar realmente complicadas. Considere o seguinte código:
Uma instância dessa classe pode ser compartilhada entre threads e eles podem adicionar coisas a ela com segurança, mas há um bug de simultaneidade (não é o único): o estado interno do objeto vaza através do
stuff
acessador. Além de ser problemático do ponto de vista do encapsulamento, ele também abre uma lata de worms de simultaneidade. Talvez alguém pegue aquele array e o passe para outro lugar, e esse código, por sua vez, pensa que agora possui esse array e pode fazer o que quiser com ele.Outro exemplo clássico de Ruby é este:
find_stuff
funciona bem na primeira vez que é usado, mas retorna outra coisa na segunda vez. Por quê? Oload_things
método acontece a pensar que é dono do hash de opções passado para ele, e fazcolor = options.delete(:color)
. Agora aSTANDARD_OPTIONS
constante não tem mais o mesmo valor. As constantes são constantes apenas naquilo que referenciam, não garantem a constância das estruturas de dados a que se referem. Pense no que aconteceria se esse código fosse executado simultaneamente.Se você evitar o estado mutável compartilhado (por exemplo, variáveis de instância em objetos acessados por vários threads, estruturas de dados como hashes e arrays acessados por vários threads) a segurança de thread não é tão difícil. Tente minimizar as partes de seu aplicativo que são acessadas simultaneamente e concentre seus esforços nisso. IIRC, em uma aplicação Rails, um novo objeto controlador é criado para cada requisição, então ele só será usado por uma única thread, e o mesmo vale para qualquer objeto modelo que você criar a partir daquele controlador. No entanto, Rails também incentiva o uso de variáveis globais (
User.find(...)
usa a variável globalUser
, você pode pensar nisso apenas como uma classe, e é uma classe, mas também é um namespace para variáveis globais), algumas delas são seguras porque são somente leitura, mas às vezes você salva coisas nessas variáveis globais porque é conveniente. Tenha muito cuidado ao usar qualquer coisa que seja globalmente acessível.É possível rodar Rails em ambientes threaded há um bom tempo, então sem ser um especialista em Rails eu ainda iria mais longe e diria que você não precisa se preocupar com segurança de thread quando se trata do Rails em si. Você ainda pode criar aplicações Rails que não sejam thread-safe fazendo algumas das coisas que mencionei acima. Quando se trata de outras joias presumem que não são thread-safe a menos que digam que são, e se dizem que são, presumem que não são, e olhe através de seu código (mas só porque você vê que eles fazem coisas como
@n ||= 1
não significa que eles não sejam thread-safe, isso é uma coisa perfeitamente legítima de se fazer no contexto certo - você deve, em vez disso, procurar coisas como estado mutável em variáveis globais, como ele lida com objetos mutáveis passados para seus métodos, e especialmente como ele lida com hashes de opções).Finalmente, ser thread não seguro é uma propriedade transitiva. Qualquer coisa que use algo que não seja seguro para thread não é seguro para threads.
fonte
STANDARD_OPTIONS = {...}.freeze
para aumentar em mutações superficiais@n = 0; 3.times { Thread.start { 100.times { @n += 1 } } }
[...], o valor da variável compartilhada posteriormente não é determinístico." - Você sabe se isso difere entre as versões do Ruby? Por exemplo, executar seu código em 1.8 fornece valores diferentes de@n
, mas em 1.9 e posteriores parece dar consistentemente@n
igual a 300.Além da resposta de Theo, eu adicionaria algumas áreas problemáticas a serem observadas em Rails especificamente, se você estiver mudando para config.threadsafe!
Variáveis de classe :
@@i_exist_across_threads
ENV :
ENV['DONT_CHANGE_ME']
Tópicos :
Thread.start
fonte
Isso não é 100% correto. O Rails thread-safe está ativado por padrão. Se você implantar em um servidor de aplicativos multiprocessos como Passenger (comunidade) ou Unicorn, não haverá nenhuma diferença. Esta mudança só diz respeito a você, se você implantar em um ambiente multi-thread como Puma ou Passenger Enterprise> 4.0
No passado, se você quisesse implantar em um servidor de aplicativo multi-threaded, você tinha que ativar config.threadsafe , que é o padrão agora, porque tudo o que ele fazia não tinha efeitos ou também se aplicava a um aplicativo Rails rodando em um único processo ( Prooflink ).
Mas se você quiser todos os benefícios de streaming do Rails 4 e outras coisas em tempo real da implantação multi-threaded, talvez você ache este artigo interessante. Como @Theo lamenta, para um aplicativo Rails, você na verdade apenas tem que omitir a mutação do estado estático durante uma solicitação. Embora seja uma prática simples de seguir, infelizmente você não pode ter certeza sobre isso para cada joia que encontrar. Pelo que me lembro, Charles Oliver Nutter do projeto JRuby tinha algumas dicas sobre isso neste podcast.
E se você quiser escrever uma programação Ruby simultânea pura, onde você precisaria de algumas estruturas de dados que são acessadas por mais de um thread, talvez você ache a gem thread_safe útil.
fonte