简体   繁体   中英

Ruby's speed of threads

I have the following code to thread-safe write into a file:

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}

If I run this code it take about 1.5 sec to write all 2000 numbers into the file. So, all is good. But if I remove the line print "." marked with "THIS LINE" is takes ages. This code needs about 12sec for only 20 threads to complete.

Now my question: why does the print speed up that code so much?

I'm not sure how you can call that thread safe at all when it's simply not. You can't use a simple variable to ensure safety because of race conditions. What happens between testing that a flag is zero and setting it to one? You simply don't know. Anything can and will eventually happen in that very brief interval if you're unlucky enough.

What might be happening is the print statement causes the thread to stall long enough that your broken locking mechanism ends up working. When testing that example using Ruby 1.9.2 it doesn't even finish, printing dots seemingly forever.

You might want to try re-writing it using 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

This is the proper Ruby way to do thread synchronization. A Mutex will not yield the lock until it is sure you have exclusive control over it. There's also the try_lock method that will try to grab it and will fail if it is already taken.

Threads can be a real nuisance to get right, so be very careful when using them.

First off, there are gems that can make this sort of thing easier. threach and jruby_threach ("threaded each") are ones that I wrote, and while I'm deeply unhappy with the implementation and will get around to making them cleaner at some point, they work fine when you have safe code.

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

or

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

You should also look at peach and parallel for other examples of easily working in parallel with multiple threads.

Above and beyond the problems already pointed out -- that your loop isn't thread-safe -- none of it matters because the code you're calling (add_to_file) isn't thread-safe. You're opening and closing the same file willy-nilly across threads, and that's gonna give you problems. I can't seem to understand what you're trying to do, but you need to keep in mind that you have absolutely no idea the order in which things in different threads are going to run.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM