Как стать автором
Обновить

Комментарии 30

потоки в Ruby очень похожи на потоки Python, в них есть достаточно много ограничений, в общем смысле их стоит использовать по количеству либо ядер (либо ядер х 2, если smp), но никак не больше, к тому же в вашем примере (хоть это довольно незначительно) если соединение диал-ап, то потоки не выполнятся параллельно (это и не только, в зависимости от задачи, может стать bottle neck в приложении), к тому же присутствует GIL (Global Interpreter Lock), про ограничения можно неплохо почитать вот тут www.infoq.com/news/2007/05/ruby-threading-futures

пс. не принимайте как критику, это дополнение к изложенному :-)
Кто определяет, какими цветами подсвечивать синтаксис?
Интересные заявления Вы делаете. Вы предлагаете забить на многопоточность, если в системе одно ядро? Откуда Вы это правило взяли: количество ядер = количество потоков?
Нужно знать свою платформу а не использовать все подряд
Current stable releases of Ruby use user space threads (also called «green threads»), which means that the Ruby interpreter takes care of everything to do with threads.… User space threads, on the other hand, can not make use of multiple cores or multiple CPUs (because the OS doesn't know about them and thus can't schedule them on these cores/CPUs).

здесь уже сама ось распараллелит задачи по ядрам или потокам, но Ruby сам этого не сможет сделать, это кстати цитата из ссылки, которую я дал, в противном случае (если у вас к примеру 2 ресурсоемкие задачи на одном ядре, то выполняться они будут точно НЕ параллельно, а либо с переключением контекста, либо друг за другом).
Во-первых, информация в цитате, которую Вы приводите, устарела. Текущий стабильный релиз (1.9.1) уже не использует userspace threads. Используются нативные потоки. Хотя все равно присутсвует GIL.

Во-вторых, возвращаясь к моему вопросу. О каком распараллеливании Вы говорите, если Вы утверждаете, что потоки надо использовать по количеству ядер? Т.е. на одноядерной машине, не более одного потока.
GIL все еще присутствует, а это означает, что ваши треды на одноядерной машине будут работать скорее всего через переключение контекста (т.е. сначала одна квант, грубо говоря, потом другая),

насчет 1.9.1 прекрасно написано в комментариях вот тут, они не параллелятся
blog.reverberate.org/2009/01/31/ruby-191-released/

разница в 1.9.1 и 1.8 заключается в том, что треды стали нативными, это повлияло и на возможности, и на скорость, но никак не решило задачу параллельности
Слушайте, это все здорово, но я же Вас не о том спрашиваю. Я хорошо представляю, как устроена поточная модель и в 1.8, и в 1.9.1. Т.ч. перестаньте, пожалуйста, сыпать ссылками и цитатами.

Меня интересует простая вещь (наверное, простая) относительно Вашего первого комментария. Что это за правило такое: количество потоков = количество ядер.

Просто Вам вот там плюсы ставят за этот комментарий. А я, честно говоря, не понимаю за что. Вы мне объясните.
я вам и объясняю, вы видимо только на плюсы смотрите, а не на суть.

правило количество потоков (тредов) = количество ядер возникло потому, что на одном ядре нормально (без переключения контекста или блокировок) можно запустить 1 тред, который ГАРАНТИРОВАНО ПОЛУЧИТ нужное количество процессорного времени, а следующий тред, запущенный на этом ядре сможет работать только тогда, когда первый тред будет выполнен.

я понятно объясняю?
Т.е. тот факт, что на одном ядре одновременно может выполняться только один поток, по-вашему является достаточным основанием для отказа от многопоточного программирования в Ruby? Объясните мне тогда, что в других языках это как по-другому? Может от него вообще в принципе стоит отказаться?

И интересно, что выражение «гарантированно получит» значит. Вы его как-то особенно выделили. Т.е. другие треды получат меньше процессорного времени, чем им нужно или они просто не выполнятся. Что с ними по-вашему произойдет?
давайте еще раз по азбуке пройдемся, если уж так.

представьте, что потоки в Ruby это рабочие, у рабочего есть пространство 1 квадратный метр, ему надо выкопать метровую яму, вы можете разместить на этом пространстве и 2 и 3 рабочих, но ЭФФЕКТИВНО эту работу будет выполнять только один

представьте, что потоки Ruby это кассир, как думаете, будет лучше, если она обслужит по одному покупателю, или будет пытаться обслужить всю толпу? ее внимание не сможет быть занято более чем 1 человеком в квант времени

так же и процессоры (ядра)

многоядерность — сравнительно недавнее изобретение, к которому еще далеко не все привыкли и научились работать, для него требуется новая архитектура (от операционных систем к приложениям), которая бы делала на 4 ядрах столько же работы, сколько могут делать 4 процессора, одинаковые по производительности, но увеличение количества пока что не повлияло на качество в той же мере

что касается других языков программирования, в Python'e, который я упоминал ситуация схожая с Ruby, Perl просто делает копию (клонирует) себя, так получается новый поток

и напоследок, я как-то заморачивался с написанием многопоточных приложений, пытаясь найти оптимальное количество тредов, приложение было подобным тому, что привел автор топика, но фишка в том, что вы запускаете несколько потоков, которые в свою очередь запускают несколько wget'ов, которые в свою очередь используют ваш интернет канал, ОСи и Ruby не сложно запустить несколько экземпляров одной программы, и тут треды — выигрыш, но bottle neck не процессор, а интернет-канал

если же вы в тредах пытаетесь рассчитывать числа Фибоначчи, то 1 тред будет кушать уже процессор, съедая его без остатка на расчеты (если ось не будет пытаться распараллеливать), не оставляя остальным ничего, либо ось будет таки распараллеливать ваши расчеты, но тогда каждый тред получит образно по 1 кванту времени и так будет по кругу, пока расчет не будет закончен
Похоже вы действительно не понимаете, зачем нужны потоки и как их можно использовать помимо задействования CPU.

Простейший пример:
вы — вебсервер.
У вас десять клиентов. Но вы не обслуживаете клиентов одновременно, пока вы заняты одним, другой готовит/передает данные. По мимо состояния «что-то делаю» потоки могут (и чаще всего) находяться в состоянии «жду».

Второй простейший пример — софтина с графической оболочкой:
Пока вычислительный поток делает задачу, GUI поток спит и ждет или ответа от вычислительного или действий пользователя.

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

Теперь поясните еще раз почему «количество потоков = ядер»
вы по-моему что-то курите, пример с вебсервером логично было бы описать в контексте socket select, но никак не многопоточности, за сим кланяюсь
А Вы уже, наверное, все, что у Вас было выкурили :)

Kernel#select и потоки — это альтернативные способы решения приведенной задачи (организации веб-сервера, да и сервера любого другого типа вообще). И Вы еще попробуйте с использованием голого select (без фреймворка типа EventMachine) реализовать более-менее сложный сервер. Чтобы он как минимум был также эффективен, как аналогичный сервер, реализованный через потоки, который и реализовать, и поддерживать куда проще.
Детской лопаточкой колодец копать
В предметке разберитесь
А Вы не могли бы поконкретнее? А то я, может быть, и рад бы разобраться в том, в чем я с Вашей точки зрения не разбираюсь. Но из Вашего коммента совершенно не ясно.
А после Ваших торгово-строительных аналогий, я бы вообще поосторожней чужие примеры (особенно вполне удачные, как в данном случае) называл нелогичными :)
Если каждый поток ест 100% ядра (ну там — бесконечные циклы гоняет например) то выигрыша не будет, да.
Однако на высокоуровневых языках принято дергать более высокооуровневые же примитивы, а не только считать что-то, от использования потоков одновременно с кучей разного IO выирыш будет очевидный — пока один поток на своем квадратном метре ждет завершения операции, второй вполне может еще что-то поделать

В свое время hyper-threading так появился
я это написал прямо над вашим комментарием в 2х последних абзацах
В рамках текущей архитектуры 1.8/1.9 GIL не уберешь :(
Бред.

1. Потоки в питоне не имеет смысла использовать ядерx2 ну никак — наличие GIL заставляет один из потоков «приостановиться» во время работы другого.

2. Для питона есть multiprocessing который решает проблему GIL и позволяет задействовать ядра.

3. «потоки встроенные в интерпретатор» о которых пишет автор сгинули в 1.8, и теперь в рубях полновесные OS потоки.

4. Использование потоков может преследовать кучу целей помимо «задействовать побольше ядер», да.
круто! Спaсибо aвтору! :*
Хорошая статья. Касательно «threads << Thread.new» есть более правильные способы делать join потоков.
Во-первых, можно

Thread.list.each { |t| t.join if t != Thread.current }
 

Условие здесь вставлено потому что никто не может вызвать join для самого себя.

А во-вторых потоки можно группировать:

requests = ThreadGroup.new
page_to_fetch.each do |page|
  requests.add Thread.new(page_to_fetch) do |url|
    # …
  end
end
 

Ну и потом пройти по группt и сделать join.
Хороший материал и грамотно подан. Спасибо!
Многопоточность в Ruby
вRuby многопоточность!
«внутрипроцессные» потоки в рубях сгинули в 1.8! Автор, поправься!
Да, в тексте описываются потоки в Ruby версии 1.8. Но с другой стороны, по сути ничего не изменилось. В 1.9 хоть и используются нативные потоки, но одно приложение может оперировать только одним потоком в одно время из-за все той же thread-safety.
Вот что сказано в последней версии книги:
Prior to Ruby 1.9, these were implemented as so-called green threads — threads were switched totally within the interpreter. In Ruby 1.9, threading is now performed by the operating system. This is an improvement, but not quite as big an improvement as you might want. Although threads can now take advantage of multiple processors (and multiple cores in a single processor), there’s a major catch. Many Ruby extension libraries are not thread safe (because they were written for the old threading model). So, Ruby compromises: it uses native operating system threads but operates only a single thread at a time. You’ll never see two threads in the same application running Ruby code truly concurrently. (You will, however, see threads busy doing (say) I/O while another thread executes Ruby code. That’s part of the point....)

Да и много ли сейчас людей используют 1.9 для разработки?
трололо. Создайте сотню «зеленых» потоков и сотню нативных и сравните нагрузку. В юниксах, поток практически = процесс со всеми вытекающими затратами на запуск и т.д.

Ну и часть страшилок статьи теряет актуальность когда контроль над потоками принимает ОС
А еще в 1.9 появились Fibers. Облегченные зеленые потоки с кооперативным шедулингом.

ruby-doc.org/core-1.9/classes/Fiber.html

Так что есть из чего выбирать.

А вообще мне больше по душе EventMachine. Хотя писать через колбеки иногда не особо приятно.
Спасибо за детальное описание!
Попробую на деле в своих грабберах и парсерах (Mechanize + proxy)
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории