Скорость потоков в Ruby - PullRequest
       21

Скорость потоков в Ruby

1 голос
/ 11 августа 2011

У меня есть следующий код для потоковой записи в файл:

threads = []
@@lock_flag = 0
@@write_flag = 0

def add_to_file
    old_i = 0
    File.open( "numbers.txt", "r" ) { |f| old_i = f.read.to_i }
    File.open( "numbers.txt", "w+") { |f| f.write(old_i+1) }
    #puts old_i
end

File.open( "numbers.txt", "w") { |f| f.write(0) } unless File.exist? ("numbers.txt")

2000.times do
    threads << Thread.new {
        done_flag = 0
        while done_flag == 0 do
            print "."           #### THIS LINE
            if @@lock_flag == 0
                @@lock_flag = 1
                if @@write_flag == 0
                    @@write_flag = 1
                    add_to_file
                    @@write_flag = 0
                    done_flag = 1
                end
                @@lock_flag = 0
            end
        end
    }
end

threads.each {|t| t.join}

Если я запускаю этот код, запись всех 2000 чисел в файл занимает около 1,5 секунд.Итак, все хорошо.Но если я уберу строку print ".", помеченную "ЭТА ЛИНИЯ", это займет много времени!Этот код требует около 12 секунд для завершения только 20 потоков.

Теперь мой вопрос: почему print ускоряет этот код так сильно?

Ответы [ 2 ]

4 голосов
/ 11 августа 2011

Я не уверен, как вообще можно назвать этот поток безопасным, когда его просто нет.Вы не можете использовать простую переменную для обеспечения безопасности из-за условий гонки.Что происходит между проверкой того, что флаг равен нулю, и установкой его в единицу?Вы просто не знаете.Все может произойти и в конечном итоге произойдет в этот очень короткий промежуток времени, если вам не повезет.

То, что может произойти, - это выражение print, заставляющее поток останавливаться достаточно долго, чтобы ваш сломанный механизм блокировки работал.При тестировании этого примера с использованием Ruby 1.9.2 он даже не заканчивается, печать точек кажется вечной.

Возможно, вы захотите попробовать переписать его, используя Mutex:

write_mutex = Mutex.new
read_mutex = Mutex.new

2000.times do
    threads << Thread.new {
        done_flag = false
        while (!done_flag) do
            print "."           #### THIS LINE
            write_mutex.synchronize do
              read_mutex.synchronize do
                add_to_file
                done_flag = true
              end
            end
        end
    }
end

Этоправильный Ruby способ сделать синхронизацию потоков.Мьютекс не снимет блокировку, пока не будет уверен, что вы обладаете исключительным контролем над ним.Есть также метод try_lock, который попытается захватить его и потерпит неудачу, если он уже занят.

Потоки могут доставлять массу неудобств, поэтому будьте очень осторожны при их использовании.

0 голосов
/ 12 августа 2011

Во-первых, есть драгоценные камни, которые могут упростить подобные вещи. threach и jruby_threach ("каждый из потоков") - те, которые я написал, и, хотя я глубоко недоволен реализацией и в какой-то момент смогу сделать их более чистыми, они прекрасно работают, когда у вас есть безопасный код.

(1..100).threach(2) {|i| do_something_with(i)} # run method in two threads

или

File.open('myfile.txt', 'r').threach(3, :each_line) {|line| process_line(line)}

Вам также следует поискать в peach и parallel другие примеры простой работы параллельно с несколькими потоками.

Помимо уже упомянутых проблем - что ваш цикл не является поточно-ориентированным - ничего из этого не имеет значения, потому что код, который вы вызываете (add_to_file), не является поточно-ориентированным. Вы открываете и закрываете один и тот же файл по всем потокам, и это создаст вам проблемы. Кажется, я не могу понять, что вы пытаетесь сделать, но вам нужно помнить, что вы абсолютно не знаете порядок, в котором будут работать вещи в разных потоках.

...