Есть ли что-то похожее на JS 'Promise.all ()' в Ruby? - PullRequest
0 голосов
/ 25 апреля 2018

Ниже приведен код, который должен быть оптимизирован:

def statistics
  blogs = Blog.where(id: params[:ids])
  results = blogs.map do |blog|
    {
      id: blog.id,
      comment_count: blog.blog_comments.select("DISTINCT user_id").count
    }
  end
  render json: results.to_json
end

Каждый запрос SQL стоит около 200 мс.Если у меня будет 10 постов в блоге, эта функция займет 2 секунды, потому что она работает синхронно.Я могу использовать GROUP BY для оптимизации запроса, но сначала я отложу это в сторону, потому что задачей может быть сторонний запрос, и меня интересует, как Ruby справляется с асинхронностью.

В Javascript, когда я хочучтобы отправить несколько асинхронных работ и дождаться их разрешения, я могу использовать Promise.all().Интересно, каковы альтернативы для языка Ruby для решения этой проблемы.

Нужна ли мне нить для этого случая?И безопасно ли это делать в Ruby?

Ответы [ 3 ]

0 голосов
/ 25 апреля 2018

Есть несколько способов решить эту проблему в ruby, включая обещания (включенные драгоценными камнями).

JavaScript выполняет асинхронное выполнение, используя цикл событий и управляемый событиями ввод-вывод. Существуют библиотеки событий для достижения того же в ruby. Одним из самых популярных является eventmachine.

Как вы упоминали, потоки также могут решить эту проблему. Безопасность потоков - это большая тема, которая усложняется разными моделями потоков в разных вариантах ruby ​​(MRI, JRuby и т. Д.). В заключение я просто скажу, что, конечно, потоки можно использовать безопасно ... бывают моменты, когда это сложно. Однако при использовании с блокирующим вводом / выводом (как в случае API или запроса к базе данных) потоки могут быть очень полезными и довольно простыми. Решение с потоками может выглядеть примерно так:

# run blocking IO requests simultaneously
thread_pool = [
  Thread.new { execute_sql_1 },
  Thread.new { execute_sql_2 },
  Thread.new { execute_sql_3 },
  # ...
]

# wait for the slowest one to finish
thread_pool.each(&:join)

У вас также есть доступ к другим моделям валют, таким как модель актера, асинхронные классы, обещания и другие, включенные драгоценными камнями, такими как concurrent-ruby.

Наконец, параллельный параллелизм ruby ​​может принимать форму множественных процессов, взаимодействующих через встроенные механизмы (drb, сокеты и т. Д.) Или через распределенные брокеры сообщений (redis, rabbitmq и т. Д.).

0 голосов
/ 25 апреля 2018

Хорошо, немного обобщив:

У вас есть список данных data, и вы хотите работать с этими данными асинхронно. Предполагая, что операция одинакова для всех записей в вашем списке, вы можете сделать это:

data = [1, 2, 3, 4] # Example data
operation = -> (data_entry) { data * 2 } # Our operation: multiply by two
results = data.map{ |e| Thread.new(e, &operation) }.map{ |t| t.value }

Разобрав его:

data = [1, 2, 3, 4]

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

operation = -> (data_entry) { data * 2 }

Определение лямбды, которая принимает один аргумент и выполняет некоторые вычисления на нем. Это может быть вызов API, запрос SQL или любая другая операция, выполнение которой занимает некоторое время. Опять же, для простоты я просто умножаю числа на 2.

results =

Этот массив будет содержать результаты всех асинхронных операций.

data.map{ |e| Thread.new(e, &operation) }...

Для каждой записи в наборе данных порождает поток, который запускает operation и передает запись в качестве аргумента. Это аргумент data_entry в лямбде.

...map{ |t| t.value }

Извлечение значения из каждого потока. Это будет ждать завершения потока, поэтому к концу этой строки все ваши данные будут там.

Лямбда

Лямбды - это просто прославленные блоки, которые вызывают ошибку, если вы передаете неверное количество аргументов. Синтаксис -> (arguments) {code} является просто синтаксическим сахаром для Lambda.new { |arguments| code }.

Когда метод принимает блок, такой как Thread.new { do_async_stuff_here }, вы также можете передать объект Lambda или Proc с префиксом &, и он будет обрабатываться таким же образом.

0 голосов
/ 25 апреля 2018

Конечно, просто посчитайте за один вызов базы данных:

blogs = Blog
  .select('blogs.id, COUNT(DISTINCT blog_comments.user_id) AS comment_count')
  .joins('LEFT JOIN blog_comments ON blog_comments.blog_id = blogs.id')
  .where(comments: { id: params[:ids] })
  .group('blogs.id')

results = blogs.map do |blog|
  { id: blog.id, comment_count: blog.comment_count }
end

render json: results.to_json

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...