У меня есть программа, которая выполняет ограниченную форму многопоточности.Он написан на 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.Есть 33M предметов с добавлением каждую секунду.Программа работает непрерывно, обрабатывая новые элементы, а иногда и повторно анализируя старые элементы.Он получает список элементов для анализа из таблицы, поэтому в начале прохода (текущий элемент) он знает следующий идентификатор элемента, который ему понадобится.
Поскольку каждый элемент независим, это идеальная цельдля многопроцессорной обработки.Самый простой способ сделать это - запустить несколько экземпляров программы на нескольких машинах.Программа высоко оптимизирована за счет профилирования, переписывания и редизайна алгоритма.Тем не менее, один экземпляр использует 100% ядра процессора, когда не требуется данных.Я запускаю 4-8 копий на двух четырехъядерных рабочих станциях.Но с такой скоростью они должны тратить время на ожидание на сервере MySQL.(Оптимизация схемы Сервер / БД - это еще одна тема.)
Я реализовал многопоточность в этом процессе исключительно во избежание блокировки вызовов SQL.Вот почему я назвал это «ограниченная многопоточность».У рабочего потока есть одна задача: отправить команду и дождаться результатов.(ОК, две задачи.)
Оказывается, есть 6 задач блокировки, связанных с 6 таблицами.Два из них читают данные, а остальные 4 записывают результаты.Они достаточно похожи, чтобы их можно было определить общей структурой задач.Указатель на эту задачу передается менеджеру пула потоков, который назначает поток для выполнения работы.Основной поток может проверить состояние задачи через структуру Задачи.
Это делает код основного потока очень простым.Когда ему нужно выполнить задачу 1, он ждет, пока задача 1 не будет занята, помещает команду SQL в задачу 1 и передает ее.Когда Task1 больше не занят, он содержит результаты (если есть).
4 задачи, которые пишут результаты, тривиальны.В главном потоке есть задача записи записей, пока он переходит к следующему элементу.Когда закончите с этим элементом, убедитесь, что предыдущая запись завершена, прежде чем начинать другую.
2 потока чтения менее тривиальны.Ничего не получится, если передать чтение потоку, а затем ждать результатов.Вместо этого эти задачи предварительно выбирают данные для следующего элемента.Таким образом, основной поток, приходя к этой задаче блокировки, проверяет, выполнена ли предварительная выборка;При необходимости ожидает завершения предварительной выборки, затем получает данные из Задачи.Наконец, он перезапускает задачу с идентификатором NEXT Item.
Идея состоит в том, чтобы задача предварительной выборки немедленно выдавала запрос и ожидала сервера MySQL.Затем основной поток может обработать текущий элемент и ко времени запуска следующего элемента необходимые данные уже находятся в задаче предварительной выборки.
Таким образом, многопоточность, пул потоков, синхронизация, структуры данных и т. Д.все сделано.И это все работает.У меня осталась проблема планирования.
Проблема планирования такова: весь выигрыш в скорости происходит при обработке текущего элемента, пока сервер выбирает следующий элемент.Мы выполняем задачу предварительной выборки перед обработкой текущего элемента, но как мы можем гарантировать, что она запускается?Планировщик ОС не знает, что для задачи предварительной выборки важно сразу же выполнить запрос, а затем он ничего не делает, кроме как ждет.
Планировщик ОС пытается быть «честным» и разрешать каждой задачебежать за назначенный интервал времени.Мой худший случай таков: основной поток получает свой фрагмент и выдает предварительную выборку, затем завершает текущий элемент и должен ждать следующего элемента.Ожидание освобождает оставшуюся часть времени, поэтому планировщик запускает поток предварительной выборки, который выдает запрос, а затем ожидает.Теперь обе темы ждут.Когда сервер сообщает, что запрос завершен, поток предварительной выборки перезапускается и запрашивает результаты (набор данных), а затем переходит в спящий режим.Когда сервер предоставляет результаты, поток предварительной выборки просыпается, помечает задачу как выполненную и завершается.Наконец, основной поток перезапускается и получает данные из завершенного задания.
Чтобы избежать этого наихудшего планирования, мне нужен какой-то способ убедиться, что запрос предварительной выборки выполняется до того, как основной поток продолжит работу с текущим элементом.,До сих пор я думал о трех способах сделать это:
Сразу после выполнения задачи предварительной выборки основной поток вызывает Sleep (0).Это должно освободить оставшуюся часть времени.Затем я надеюсь , что планировщик запускает поток предварительной выборки, который выдаст запрос и затем подождет.Затем планировщик должен перезапустить основной поток (я надеюсь.) Как бы плохо это ни звучало, на самом деле это работает лучше, чем ничего.
Я мог бы выдать поток предварительной выборки с более высоким приоритетом, чемосновная нить.Это должно привести к тому, что планировщик запустит его сразу, даже если он должен выгружать основной поток.Это также может иметь нежелательные последствия.Для фонового рабочего потока кажется неестественным получение более высокого приоритета.
Возможно, я могу выполнить запрос асинхронно.То есть отдельная отправка запроса от получения результатов.Таким образом, основной поток мог отправить предварительную выборку с помощью mysql_send_query (без блокировки) и продолжить работу с текущим элементом.Затем, когда ему понадобится следующий элемент, он вызовет mysql_read_query, который будет блокироваться, пока не будут доступны данные.
Обратите внимание, что решение 3 даже не использует рабочий поток.Это выглядит как лучший ответ, но требует переписать некоторый низкоуровневый код.В настоящее время я ищу примеры такого асинхронного доступа клиент-сервер.
Я также хотел бы получить какие-либо опытные мнения об этих подходах.Я что-то пропустил или я делаю что-то не так?Обратите внимание, что это все рабочий код.Я не спрашиваю, как это сделать, но как это сделать лучше / быстрее.