2014-10-28 5 views
3

Я написал код в ruby ​​для обработки элементов массива через threadpool. В этом процессе я предварительно выделил массив результатов, который имеет тот же размер, что и переданный массив. В threadpool я назначаю элементы в предварительно распределенном массиве, но индексы этих элементов гарантированно будут уникальными. Имея это в виду, мне нужно окружить задание Mutex#synchronize?Является Ruby Array # [] = threadsafe для предварительно распределенного массива? Может ли это сделать беззаконным?

Пример:

SIZE = 1000000000 
def collect_via_threadpool(items, pool_count = 10) 
    processed_items = Array.new(items.count, nil) 
    index = -1 
    length = items.length 
    mutex = Mutex.new 
    items_mutex = Mutex.new 
    [pool_count, length, 50].min.times.collect do 
    Thread.start do 
     while (i = mutex.synchronize{index = index + 1}) < length do 


      processed_items[i] = yield(items[i]) 
      #^do I need to synchronize around this? `processed_items` is preallocated 

     end 
    end 
    end.each(&:join) 
    processed_items 
end 

items = collect_via_threadpool(SIZE.times.to_a, 100) do |item| 
    item.to_s 
end 

raise unless items.size == SIZE 

items.each_with_index do |item, index| 
    raise unless item.to_i == index 
end 

puts 'success' 

(Этот тест код занимает много времени для запуска, но, как представляется, печатать «успех» каждый раз.)

Похоже, что я хотел бы, чтобы окружать Array#[]= с Mutex#synchronize только для безопасности, но мой вопрос:

В пределах спецификации Ruby этот код определен как безопасный?

+0

Это зависит от того, какие реализации Ruby вы собираетесь использовать. MRI Ruby (классический) имеет глобальную блокировку интерпретатора. Я не думаю, что Ruby сам накладывает на это какие-либо гарантии, хотя я не могу дать хорошую цитату об этом (вот почему это не ответ). –

ответ

1

Ничего в Ruby не указано указано, чтобы быть потокобезопасным, кроме Mutex (и, следовательно, ничего из него). Если вы хотите узнать, соответствует ли ваш код , вам нужно посмотреть, как ваша реализация обрабатывает потоки и массивы.

Для MRI вызов Array.new(n, nil) действительно выделяет память для всего массива, поэтому, если ваши потоки гарантированно не будут делиться индексами, ваш код будет работать. Это безопасно, поскольку несколько потоков работают с отдельными переменными без мьютекса.

Однако для других реализаций Array.new(n, nil) может не выделять целый массив, а присвоение индексов позже может включать перераспределения и копии памяти, которые могут катастрофически разрушаться.

Так что, пока ваш код может работать (по крайней мере, в МРТ), не полагайтесь на него. Пока мы говорим о теме, нити Ruby даже не указываются на , которые фактически работают параллельно. Поэтому, если вы пытаетесь избежать мьютексов, потому что считаете, что можете увидеть повышение производительности, возможно, вам следует пересмотреть свой подход.

+0

Мне действительно интересно узнать код в '# collect_via_threadpool', и передаваемый ему блок может делать IO, и в этом случае есть преимущество для threadpool. –

+0

Мое мнение заключалось в том, что если есть польза для threadpool, это должно происходить из работы, которую выполняют потоки, а не из записи в общий массив без мьютекса. Похоже, что в вашем случае вы могли бы добавить мьютекс, чтобы получить безопасность _guaranteed_ thread с неудовлетворительными штрафами за производительность. Почему бы не сделать это? – Max

+0

Нет, я определенно думаю, что вы правы, просто вопрос не в том, «что лучше делать в этом случае?». Это более общий вопрос о том, поддерживает ли Ruby это. Это звучит как «нет». –