Группы потоков в реализации пула потоков в Unix
Данная статья не относится к реализации пула потоков в Windows. В Windows MariaDB использует собственный пул потоков, созданный с помощью API CreateThreadpool, который имеет собственные методы распределения потоков между процессорами.
В Unix реализация пула потоков использует объекты, называемые группами потоков, для разделения подключений клиентов на множество независимых наборов потоков. Системная переменная thread_pool_size определяет количество групп потоков в системе. В целом, цель реализации групп потоков заключается в том, чтобы на каждом процессоре системы в каждый момент времени работал один поток. Поэтому значение системной переменной thread_pool_size по умолчанию автоматически настраивается на количество процессоров в системе.
При установке значения системной переменной thread_pool_size во время запуска системы максимальное значение равно 100000. Однако не рекомендуется устанавливать его так высоко. При динамической установке значения максимальное значение равно 128 или значению, установленной во время запуска системы — в зависимости от того, какое значение больше. Его можно изменить динамически с помощью SET GLOBAL. Например:
SET GLOBAL thread_pool_size=32;
Его также можно установить в группе опций сервера группы опций в файле опций файле опций до запуска сервера. Например:
[mariadb] .. thread_handling=pool-of-threads thread_pool_size=32
Если по какой-либо причине вы не хотите, чтобы MariaDB использовала все процессоры системы, вы можете установить меньшее значение, чем количество процессоров. Например, это будет логично, если процесс MariaDB Server ограничен определёнными процессорами с помощью утилиты taskset в Linux.
Если вы установите значение равным количеству процессоров и обнаружите, что процессоры по-прежнему используются недостаточно эффективно, попробуйте увеличить значение.
Системная переменная thread_pool_size обычно оказывает наибольшее влияние на производительность. Она примерно эквивалентна количеству потоков, которые могут выполняться одновременно. В данном случае «выполнение» означает использование процессора, а не ожидание. Если подключение клиента по какой-либо причине нуждается в ожидании, то перед этим он разбудит другое подключение клиента в группе потоков.
Одной из причин низкой загрузки процессора в редких случаях может быть то, что пул потоков не всегда получает информацию о том, когда поток будет ожидать. Например, некоторые ожидания, такие как ошибка страницы или промах в кэше буферов ОС, не могут быть обнаружены MariaDB. До MariaDB 10.0 также могли пропускаться ожидания, связанные с сетевым вводом-выводом.
Распределение подключений клиентов между группами потоков
При создании нового подключения клиента его группа потоков определяется следующим образом:
thread_group_id = connection_id % thread_pool_size
Значение connection_id в вышеприведённом вычислении — это то же монотонно возрастающее число, которое можно использовать для идентификации подключений в выводе SHOW PROCESSLIST или таблице information_schema.PROCESSLIST.
Данное вычисление должно распределять подключения клиентов по группам потоков по круговому методу. В целом это должно привести к равномерному распределению подключений клиентов среди групп потоков.
Типы потоков
Потоки группы потоков
Группы потоков имеют два разных типа потоков: поток-слушатель и рабочие потоки.
- Рабочие потоки группы потоков фактически выполняют работу от имени подключений клиентов. Группа потоков может иметь много рабочих потоков, но обычно только один из них будет активно работать в каждый момент времени. Это не всегда так. Например, группа потоков может стать перенасыщенной, если поток таймера пула потоков обнаружит, что группа потоков заблокирована. Это объясняется более подробно в следующих разделах.
- Поток-слушатель группы потоков следит за событиями ввода-вывода и распределяет работу рабочим потокам. Если он обнаруживает, что требуется обработать запрос, он может разбудить спящий рабочий поток в группе потоков, если таковой имеется. Если поток-слушатель — единственный поток в группе потоков, он также может создать новый рабочий поток. Если нужно обработать только один запрос и если системная переменная
thread_pool_dedicated_listenerне включена, то поток-слушатель также может стать рабочим потоком и обработать запрос самостоятельно. Это помогает уменьшить издержки, которые могут возникнуть при чрезмерном пробуждении спящих рабочих потоков и чрезмерном создании новых рабочих потоков.
Глобальные потоки
Пул потоков имеет один глобальный поток: поток таймера. Поток таймера выполняет задачи, такие как:
- Проверка каждой группы потоков на наличие блокировок.
- Обеспечение наличия потока-слушателя в каждой группе потоков.
Создание потоков
Новый поток создаётся в группе потоков в следующих сценариях.
Во всех перечисленных ниже сценариях реализация пула потоков предпочитает пробудить спящий рабочий поток, который уже существует в группе потоков, вместо создания нового потока.
Создание рабочего потока потоком-слушателем
Поток-слушатель группы потоков может создать новый рабочий поток, когда у него больше запросов на подключение клиентов для распределения, но доступных рабочих потоков для работы с запросами нет. Это может помочь гарантировать, что у группы потоков всегда достаточно потоков для поддержания одного активного рабочего потока.
Поток-слушатель группы потоков создаёт новый рабочий поток, если выполнены все следующие условия:
- Поток-слушатель получает запрос на подключение клиента, который требуется обработать.
- В очереди обработки группы потоков есть дополнительные запросы на подключение клиента, которые поток-слушатель всё ещё должен распределить рабочим потокам, поэтому поток-слушатель не должен стать рабочим потоком.
- В группе потоков нет активных рабочих потоков.
- В группе потоков нет спящих рабочих потоков, которые поток-слушатель может разбудить.
- И также выполняется одно из следующих условий:
- Вся система пула потоков имеет менее
thread_pool_max_threads. - В группе потоков менее двух потоков. Это гарантирует, что каждая группа потоков может иметь как минимум два потока, даже если
thread_pool_max_threadsуже достигнуто или превышено.
- Вся система пула потоков имеет менее
Создание потоков рабочими потоками во время ожидания
Рабочий поток группы потоков может создать новый рабочий поток, когда поток должен ждать чего-либо, и в очереди группы потоков есть дополнительные запросы на подключение клиента, но доступных рабочих потоков для работы с ними нет. Это может помочь гарантировать, что у группы потоков всегда достаточно потоков для поддержания одного активного рабочего потока. Для большинства рабочих нагрузок это является основным механизмом создания новых рабочих потоков.
Рабочий поток группы потоков создаёт новый поток, если выполнены все следующие условия:
- Рабочий поток должен ждать выполнения некоторого запроса. Например, он может ожидать ввода-вывода с диска, ожидать блокировки или просто ждать завершения запроса, который вызвал функцию
SLEEP(). - В группе потоков нет активных рабочих потоков.
- В группе потоков нет спящих рабочих потоков, которые рабочий поток может разбудить.
- И также выполняется одно из следующих условий:
- Вся система пула потоков имеет менее
thread_pool_max_threads. - В группе потоков менее двух потоков. Это гарантирует, что каждая группа потоков может иметь как минимум два потока, даже если
thread_pool_max_threadsуже достигнуто или превышено.
- Вся система пула потоков имеет менее
- И также выполняется одно из следующих условий:
- В очереди обработки группы потоков есть дополнительные запросы на подключение клиента, которые поток-слушатель всё ещё должен распределить рабочим потокам. В этом случае новый поток предназначен для работы как рабочий поток.
- В группе потоков в данный момент нет потока-слушателя. Например, если системная переменная
thread_pool_dedicated_listenerне включена, то поток-слушатель группы потоков может стать рабочим потоком, чтобы он мог обработать запрос на подключение клиента. В этом случае новый поток может стать потоком-слушателем группы потоков.
Создание потока-слушателя потоком таймера
Поток таймера пула потоков может создать новый поток-слушатель для группы потоков, когда в группе потоков есть дополнительные запросы на подключение клиентов, которые нужно распределить, но в группе потоков нет потока-слушателя для их распределения. Это может помочь гарантировать, что группа потоков не пропустит запросы на подключение клиентов из-за отсутствия потока-слушателя.
Поток таймера пула потоков создаёт новый поток-слушатель для группы потоков, если выполнены все следующие условия:
- Группа потоков не обрабатывала никаких событий ввода-вывода с момента последней проверки потоком таймера.
- В настоящее время в группе потоков нет потока-слушателя. Например, если системная переменная
thread_pool_dedicated_listenerне включена, то поток-слушатель группы потоков может стать потоком-рабочим, чтобы обрабатывать запросы подключения клиентов. В этом случае новый поток может стать потоком-слушателем группы. - В группе потоков нет спящих потоков-рабочих, которые поток таймера может разбудить.
- И выполняется одно из следующих условий:
- Весь пул потоков содержит меньше, чем
thread_pool_max_threads. - В группе потоков меньше двух потоков. Это гарантирует, что каждая группа потоков может иметь как минимум два потока, даже если
thread_pool_max_threadsуже достигнуто или превышено.
- Весь пул потоков содержит меньше, чем
- Если в группе потоков уже есть активные потоки-рабочие, то необходимо также выполнить следующее условие:
- Поток-рабочий для группы потоков не был создан в течение интервала ограничения скорости.
Создание потока-рабочего потоком таймера во время остановок
Поток таймера пула потоков может создать новый поток-рабочий для группы потоков, когда группа потоков останавливается. Это может помочь гарантировать, что длительный запрос не монополизирует свою группу потоков.
Поток таймера пула потоков создает новый поток-рабочий для группы потоков, если выполнены все следующие условия:
- Поток таймера считает, что группа потоков останавливается. Это означает, что выполнены следующие условия:
- В очереди задач группы потоков больше запросов подключения клиентов, которые поток-слушатель еще должен распределить потокам-рабочим.
- Запросы подключения клиентов не разрешены для обработки с момента последней проверки на остановку потоком таймера.
- В группе потоков нет спящих потоков-рабочих, которые поток таймера может разбудить.
- И выполняется одно из следующих условий:
- Весь пул потоков содержит меньше, чем
thread_pool_max_threads. - В группе потоков меньше двух потоков. Это гарантирует, что каждая группа потоков может иметь как минимум два потока, даже если
thread_pool_max_threadsуже достигнуто или превышено.
- Весь пул потоков содержит меньше, чем
- Поток-рабочий не был создан для группы потоков в течение интервала ограничения скорости.
Ограничение скорости создания потоков
В некоторых из перечисленных выше сценариев поток создается только в группе потоков, если для этой группы не создавались новые потоки в течение интервала ограничения скорости. Интервал ограничения скорости зависит от количества потоков, уже находящихся в группе потоков.
В MariaDB 10.5 и более поздних версиях создание потоков не ограничивается, пока группа потоков не имеет более чем 1 + thread_pool_oversubscribe потоков:
| Количество потоков в группе потоков | Интервал ограничения скорости (миллисекунды) |
|---|---|
0-(1 + thread_pool_oversubscribe) |
0 |
| 4-7 | 50 * THROTTLING_FACTOR
|
| 8-15 | 100 * THROTTLING_FACTOR
|
| 16-65536 | 20 * THROTTLING_FACTOR
|
THROTTLING_FACTOR = (thread_pool_stall_limit / MAX (500,thread_pool_stall_limit))
В MariaDB 10.4 и ранее создание потоков ограничивается, когда группа потоков имеет более чем 3 потока:
| Количество потоков в группе потоков | Интервал ограничения скорости (миллисекунды) |
|---|---|
| 0-3 | 0 |
| 4-7 | 50 |
| 8-15 | 100 |
| 16-65536 | 200 |
Остановки группы потоков
В пуле потоков есть функция, которая позволяет ему определять, выполняет ли клиентский запрос длительную операцию, которая может монополизировать свою группу потоков. Если клиентский запрос монополизирует свою группу потоков, это может помешать другим клиентским запросам в группе потоков выполнить свои запросы. Другими словами, группа потоков будет казаться остановленной.
Эта функция обнаружения остановок реализуется путем создания потока таймера, который периодически проверяет, остановлены ли какие-либо группы потоков. Для всего пула потоков используется только один поток таймера. Системная переменная thread_pool_stall_limit определяет количество миллисекунд между каждой проверкой на остановку, выполняемой потоком таймера. Значение по умолчанию — 500. Его можно изменять динамически с помощью SET GLOBAL. Например:
SET GLOBAL thread_pool_stall_limit=300;
Его также можно задать в группе опций сервера в файле опций перед запуском сервера. Например:
[mariadb] .. thread_handling=pool-of-threads thread_pool_size=32 thread_pool_stall_limit=300
Поток таймера считает группу потоков остановленной, если выполняется следующее условие:
- В очереди задач группы потоков больше запросов подключения клиентов, которые поток-слушатель еще должен распределить потокам-рабочим.
- Запросы подключения клиентов не разрешены для обработки с момента последней проверки на остановку потоком таймера.
Это указывает на то, что один или несколько подключений клиентов, в настоящее время использующих активные потоки-рабочие, могут монополизировать группу потоков и препятствовать выполнению запросов подключенных клиентов. Когда поток таймера обнаруживает, что группа потоков остановлена, он пробуждает спящий поток-рабочий в этой группе, если такой есть. Если нет, то он создает новый поток-рабочий в группе. Это временно позволяет нескольким подключенным клиентам в группе потоков выполнять запросы параллельно.
Системная переменная thread_pool_stall_limit по существу определяет предел для того, что такое «быстрый запрос». Если запрос занимает более thread_pool_stall_limit времени, то пул потоков, вероятно, посчитает его слишком медленным, и он либо пробудит спящий рабочий поток, либо создаст новый рабочий поток, чтобы позволить другому подключенному клиенту в группе потоков выполнить запрос параллельно.
В общем случае изменение значения системной переменной thread_pool_stall_limit имеет следующий эффект:
- Установка более высоких значений может помочь избежать запуска слишком многих параллельных потоков, если ожидается, что множество подключенных клиентов будут выполнять длительные запросы.
- Установка более низких значений может помочь предотвратить тупиковые ситуации.
Переподписка на группу потоков
Если поток таймера обнаружил остановку в группе потоков, он либо разбудит спящий поток-рабочий, либо создаст новый поток-рабочий в этой группе. В этот момент группа потоков будет иметь несколько активных потоков-рабочих. Другими словами, группа потоков будет переподписана.
Можно ожидать, что пул потоков завершит один из потоков-рабочих, когда приостановленное подключение клиента закончит свою работу, чтобы в группе потоков снова остался только один активный поток-рабочий. Однако это не всегда происходит. После того, как группа потоков переподписана, системная переменная thread_pool_oversubscribe определяет верхний предел, после достижения которого потоки-рабочие начинают завершаться после завершения работы для подключенных клиентов. Значение по умолчанию — 3. Его можно изменять динамически с помощью SET GLOBAL. Например:
SET GLOBAL thread_pool_oversubscribe=10;
Его также можно задать в группе опций сервера в файле опций перед запуском сервера. Например:
[mariadb] .. thread_handling=pool-of-threads thread_pool_size=32 thread_pool_stall_limit=300 thread_pool_oversubscribe=10
Для ясности, системная переменная thread_pool_oversubscribe не играет никакой роли в создании новых потоков-рабочих. Системная переменная thread_pool_oversubscribe используется только для определения того, сколько потоков-рабочих должно оставаться активными в группе потоков после того, как группа потоков уже переподписана из-за остановок.
В общем случае значение по умолчанию для 3 должно быть достаточным для большинства пользователей. Большинству пользователей не нужно изменять значение системной переменной thread_pool_oversubscribe.
© 2023 MariaDB
Licensed under the Creative Commons Attribution 3.0 Unported License and the GNU Free Documentation License.
https://mariadb.com/kb/en/thread-groups-in-the-unix-implementation-of-the-thread-pool/