Как я могу запустить поток запроса SQL, а затем выполнить другую работу до получения результатов?

У меня есть программа, которая выполняет ограниченную форму многопоточности. Он написан на Delphi и использует libmysql.dll (C API) для доступа к серверу MySQL. Программа должна обработать длинный список записей, занимая ~ 0,1 с на запись. Думайте об этом как об одной большой петле. Весь доступ к базе данных осуществляется рабочими потоками, которые либо предварительно выбирают следующие записи, либо записывают результаты, поэтому основному потоку не нужно ждать.

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

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

Я вроде исправил это, вызвав sleep (0) сразу после запуска потока предварительной выборки. Таким образом, основной поток передает оставшуюся часть своего временного отрезка, надеясь, что поток предварительной выборки теперь будет запущен, отправляя запрос. Затем этот поток перейдет в спящий режим во время ожидания, что позволит основному потоку снова запуститься.
Конечно, в ОС работает гораздо больше потоков, но это действительно работало до некоторой степени.

Я действительно хочу, чтобы основной поток отправил запрос, а затем рабочий поток ждал результатов. . Используя libmysql.dll, я вызываю

result := mysql_query(p.SqlCon,pChar(p.query));

в рабочем потоке. Вместо этого я бы хотел, чтобы основной поток вызывал что-то вроде

mysql_threadedquery(p.SqlCon,pChar(p.query),thread);

, который передал бы задачу, как только данные будут утеряны.

Кто-нибудь знает что-нибудь подобное?

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

Может быть, он там, а я просто не знаю об этом. Просветите меня, пожалуйста.

Добавлен вопрос:

Кто-нибудь думает, что эту проблему можно решить, запустив поток предварительной выборки с более высоким приоритетом, чем основной поток? Идея состоит в том, что предварительная выборка немедленно прервет основной поток и отправит запрос. Затем он будет спать, ожидая ответа сервера. Тем временем будет выполняться основной поток.

Добавлено: Подробная информация о текущей реализации

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

Поскольку каждый элемент независим, это идеальная цель для многопроцессорной обработки. Самый простой способ сделать это - запустить несколько экземпляров программы на нескольких машинах. Программа сильно оптимизирована за счет профилирования, переписывания и изменения алгоритма. Тем не менее, при отсутствии нехватки данных один экземпляр использует 100% ядра ЦП. Я запускаю 4-8 копий на двух четырехъядерных рабочих станциях. Но с такой скоростью они должны ждать на сервере MySQL. (Оптимизация схемы Сервер / БД - другая тема.)

Я реализовал многопоточность в процессе исключительно для того, чтобы избежать блокировки вызовов SQL. Вот почему я назвал это «ограниченной многопоточностью». У рабочего потока одна задача: отправить команду и дождаться результатов. (Хорошо, две задачи.)

Оказывается, есть 6 блокирующих задач, связанных с 6 таблицами. Два из них считывают данные, а остальные 4 записывают результаты. Они достаточно похожи, чтобы их можно было определить общей структурой задач. Указатель на эту Задачу передается диспетчеру пула потоков, который назначает поток для выполнения работы. Основной поток может проверять статус задачи через структуру задач.

Это делает код основного потока очень простым. Когда ему нужно выполнить Task1, он ждет, пока Task1 не будет занято, помещает команду SQL в Task1 и передает ее. Когда Task1 больше не занято, оно содержит результаты (если есть).

Четыре задачи, которые записывают результаты, тривиальны. Основной поток имеет записи задачи записи, пока он переходит к следующему элементу. Когда выполняется с этим элементом, он обеспечивает завершение предыдущей записи перед запуском другой.

2 потока чтения менее тривиальны. Ничего не получится, если передать чтение потоку и затем дождаться результатов. Вместо этого эти задачи предварительно выбирают данные для следующего элемента. Итак, основной поток, подходящий к этой блокирующей задаче, проверяет, выполнена ли предварительная выборка; При необходимости ожидает завершения предварительной выборки, а затем берет данные из задачи. Наконец, он повторно выдает Задачу с ИДЕНТИФИКАТОРом СЛЕДУЮЩЕГО элемента.

Идея состоит в том, чтобы задача предварительной выборки немедленно выдавала запрос и ожидала сервера MySQL. Затем основной поток может обработать текущий элемент, и к тому времени, когда он начнется со следующего элемента, необходимые ему данные будут в задаче предварительной выборки.

Таким образом, потоки, пул потоков, синхронизация, структуры данных и т. Д. - все это сделано. И все это работает. То, что у меня осталось, - это проблема планирования.

Проблема планирования заключается в следующем: весь выигрыш в скорости заключается в обработке текущего элемента, в то время как сервер получает следующий элемент. Мы выполняем задачу предварительной выборки перед обработкой текущего элемента, но как гарантировать ее запуск? Планировщик ОС не знает, что это ' Важно, чтобы задача предварительной выборки выдавала запрос сразу, а затем она ничего не делала, кроме как ждать.

Планировщик ОС пытается быть «честным» и позволить каждой задаче выполняться в течение назначенного отрезка времени. Мой худший случай таков: основной поток получает свой фрагмент и выполняет предварительную выборку, затем завершает текущий элемент и должен ждать следующего элемента. Ожидание освобождает оставшуюся часть своего временного интервала, поэтому планировщик запускает поток предварительной выборки, который выдает запрос, а затем ждет. Теперь оба потока ждут. Когда сервер сигнализирует, что запрос выполнен, поток предварительной выборки перезапускается и запрашивает Результаты (набор данных), а затем засыпает. Когда сервер предоставляет результаты, поток предварительной выборки пробуждается, отмечает выполнение задачи и завершается. Наконец, основной поток перезапускается и берет данные из завершенной задачи.

Чтобы избежать этого худшего случая планирования, мне нужен способ гарантировать, что запрос предварительной выборки будет выполнен до того, как основной поток продолжит выполнение текущего элемента. До сих пор я придумал три способа сделать это:

  1. Сразу после выполнения задачи предварительной выборки основной поток вызывает Sleep (0). Это должно освободить оставшуюся часть своего временного отрезка. Затем я надеюсь , что планировщик запустит поток предварительной выборки, который выдаст запрос и затем будет ждать. Затем планировщик должен перезапустить основной поток (я надеюсь.) Как бы плохо это ни звучало, на самом деле это работает лучше, чем ничего.

  2. Я мог бы запустить поток предварительной выборки с более высоким приоритетом, чем основной поток. Это должно привести к тому, что планировщик сразу запустит его, даже если он должен вытеснить основной поток. Это также может иметь нежелательные эффекты. Кажется неестественным, что фоновый рабочий поток получает более высокий приоритет.

  3. Я мог бы выполнить запрос асинхронно. То есть отдельная отправка запроса от получения результатов. Таким образом, я мог бы заставить основной поток отправить предварительную выборку с помощью mysql_send_query (без блокировки) и продолжить работу с текущим элементом. Затем, когда ему понадобится следующий элемент, он вызовет mysql_read_query, который будет блокироваться, пока данные не станут доступны.

Обратите внимание, что решение 3 даже не использует рабочий поток. Это похоже на лучший ответ, но требует переписывания некоторого низкоуровневого кода. Я сейчас ищу примеры такого асинхронного доступа клиент-сервер.

Я также хотел бы получить любое опытное мнение об этих подходах. Я что-то пропустил или что-то делаю не так? Обратите внимание, что это весь рабочий код. Я' m не спрашивает, как это сделать, а как сделать лучше / быстрее.

6
задан Guy Gordon 18 November 2010 в 15:50
поделиться