Blog do TaQ

Ruby 1.9 com threads não bloqueantes

Publicado em Developer

Ruby

Eu estou sempre de olho em algumas coisinhas novas implementadas no Ruby 1.9 (inclusive vou falar de algumas delas no MinasOnRails, apareça lá para comer um pão-de-queijo com a gente!) e uma que eu estava de olho era na questão de que se uma thread que fosse bloqueada através de algum recurso do sistema mais chatinho, bloquearia a execução das outras. Isso definitivamente não é uma coisa legal, e como o anúncio do Ruby 1.9 com native threads para o Natal desse ano eu decidi rodar um teste aqui para ver como elas estavam se comportando.

O teste é simples e consiste em fazer duas threads ler dois arquivos texto enquanto outra lê um FIFO. Para criar os arquivos e o FIFO podemos fazer dessa maneira:

echo "thread_test file" > thread_test.txt
echo "thread_test2 file" > thread2_test.txt
mkfifo thread_test.fifo

Nesse ponto temos nossos dois arquivos, com conteúdo mínimo e um FIFO vazio. Fazendo um teste simples no FIFO, abra dois terminais no mesmo diretório e digite os seguintes comandos, cada um em um terminal:

tail -f thread_test.fifo
echo "oi" > thread_test.fifo

Como podemos ver, é mostrado o conteúdo e esvaziado o FIFO com o tail. É esse o ponto onde ocorre o bloqueio. Vamos rodar o seguinte código, primeiro na versão atual do Ruby, que usa green threads:

 1 files    = %w(thread_test.txt thread_test.fifo thread_test2.txt)
 2 threads  = []
 3 
 4 files.each_with_index do |item,index|
 5    threads << Thread.new do
 6       while true
 7          puts "(#{index}) Reading #{item} on #{Time.now} ..."
 8          puts File.read(item)
 9          puts "-"*50
10          sleep 1
11       end
12    end
13 end
14 threads.first.join
ruby thread_test.rb 
(0) Reading thread_test.txt on Tue Nov 20 10:41:52 -0200 2007 ...
(1) Reading thread_test.fifo on Tue Nov 20 10:41:52 -0200 2007 ...

Oh-oh. Se não interrompermos o processamento, o programa vai ficar parado ali para sempre. Como podemos ver, o número 1 ali é a thread que lê o FIFO, e como não há conteúdo, ela fica parada bloqueando as outras. Argh. Se enviarmos conteúdo com um echo para o FIFO de maneira similar à mostrada acima, o bloqueio é removido somente até a thread rodar novamente. Nada bom. Inclusive, para interromper o processamento é necessário dar CTRL+C e enviar alguma coisa para o FIFO, senão só fechando o terminal.

Mas seus problemas acabaram! Com o Ruby 1.9 e a correção que o Koichi fez no último commit, rodando o código vamos ter:

ruby1.9 thread_test.rb 
(0) Reading thread_test.txt on 2007-11-20 10:47:00 -0200 ...
thread_test file
--------------------------------------------------
(1) Reading thread_test.fifo on 2007-11-20 10:47:00 -0200 ...
(2) Reading thread_test2.txt on 2007-11-20 10:47:00 -0200 ...
thread_test2 file
--------------------------------------------------
(0) Reading thread_test.txt on 2007-11-20 10:47:01 -0200 ...
thread_test file
--------------------------------------------------
(2) Reading thread_test2.txt on 2007-11-20 10:47:01 -0200 ...
thread_test2 file
--------------------------------------------------
oi
--------------------------------------------------
(0) Reading thread_test.txt on 2007-11-20 10:47:02 -0200 ...
thread_test file
--------------------------------------------------
(2) Reading thread_test2.txt on 2007-11-20 10:47:02 -0200 ...
thread_test2 file
--------------------------------------------------
(1) Reading thread_test.fifo on 2007-11-20 10:47:03 -0200 ...
(0) Reading thread_test.txt on 2007-11-20 10:47:03 -0200 ...
thread_test file
--------------------------------------------------
(2) Reading thread_test2.txt on 2007-11-20 10:47:03 -0200 ...
thread_test2 file
--------------------------------------------------
thread_test.rb:14:in `join': Interrupt
        from thread_test.rb:14:in `
'

Como podemos ver, sem bloqueios agora. A thread foi ler o FIFO (1), não encontrou nada, ficou esperando mas sem bloquear as outras. Somente depois que os arquivos foram lidos duas vezes eu enviei o "oi" para o FIFO, que pode ser visto em uma linha ali em cima sozinho. Segundo o Koichi disse, ele modificou o Global VM Lock (GVL, o terror de muita gente) e quer revisar a IO do Ruby com as natives threads, será que dá tempo até o Natal? Se não der não me culpem! :-D

Para finalizar, o Charles Nutter comentou aqui que esse tipo de comportamento é possível com a versão atual do Ruby, mas já que o 1.9 está chegando com todo o conceito novo das native threads (mesmo que ainda use alguns conceitos anteriores como o global lock) acredito que esse fix do Koichi só deva aparecer nessa versão vindoura mesmo. Tomara que a revisão mencionada pelo Koichi consiga agradar a Gregos e Troianos, tem uma turma meio brava e desapontada com os rumos da coisa.


Tags:


Comentários

comments powered by Disqus

Twitter