Перейти к содержанию
Fire Monkey от А до Я
  • 0

Быстродействие при использовании TCrititcalSection и TThread


Камышев Александр

Вопрос

Windows, FMX

Возможно не совсем в тему форума, вопрос по архитектуре серверных служб, хотелось бы услышать мнения.

Ситуация:

IdHTTTPServer на каждый запрос создает поток, в этом потоке не обойтись без обращения к пулу данных в памяти. Пул - несколько наборов актуальных данных. Наборы данных асинхронно получаются из БД, имеют связи многие ко многим, один ко многим и периодически кэшируются в память в основном потоке. Т.к. обращение к пулу из потока - соответственно пул должен быть потокозащищенным. После обработки запроса, данные также отправляются в  основном потоке в очередь БД.

1. Если весь пул закрыть в TCriticalSection - то на время использования его одним потоком все остальные будут ожидать. Обращение к очереди БД из потока получается также должно быть потокозащищенным. Не изящно.

2. Можно задачу обработки положить в некую потокозащищенную очередь и остановить поток c помощью TSimpleEvent->WaitFor( INFINITE ). Далее в основном потоке работать с пулом данных и очередью БД без критических сессий и, после получения результата, запустить поток SetEvent(). Код будет проще и понятней, однако задачи будут выполняться синхронно, последовательно как и в первом случае.

3. Можно закрывать TCriticalSection отдельные наборы данных, это возможно несколько увеличит быстродействие (не факт!), усложнит код и увеличит вероятность deadlock, т.к. для обработки одного запроса используется несколько наборов данных. Deadlock не будет, если перед обращением к следующему набору ( critical_section->Enter() ) копировать что необходимо из предыдущего и отпускать его ( critical_section->Leave() ) - тут становится важна стоимость операторов копирования.При больших объемах, стоимость копирования может перекрыть весь профит от частного обращения к наборам.

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

Вопросы:

1. Какой вариант предпочтительней? Есть стандартные схемы?

2. Как влияет количество ядер, процессоров, на быстродействие во втором и третьем случае?

Ссылка на комментарий

Рекомендуемые сообщения

  • 0
1 час назад, Камышев Александр сказал:

1. Если весь пул закрыть в TCriticalSection - то на время использования его одним потоком все остальные будут ожидать. Обращение к очереди БД из потока получается также должно быть потокозащищенным. Не изящно.

Зато - сохраняется ссылочная целостность в этих связанных наборах. А вот вариант 3 ее лишен.

Если обработка данных потоком подразумевает длительные операции - возможно, стоит использовать что-то вроде п.2? Необходимые данные копируем (с блокировкой пула), работаем с ними, копируем обратно (с блокировкой пула). ?

Ссылка на комментарий
  • 0
3 часа назад, Камышев Александр сказал:

1. Если весь пул закрыть в TCriticalSection - то на время использования его одним потоком все остальные будут ожидать. Обращение к очереди БД из потока получается также должно быть потокозащищенным. Не изящно.

Не совсем понятно с размером пула, но что мешает потоку читать данные из пула и самому их обрабатывать и не понятно, что такое "очередь к БД" и как она связана с вашим пулом.

Ссылка на комментарий
  • 0

Размер пула - четыре std::map размером до 10000 с различными структурами, которые описывают оборудование, задания, прошивки, ссылки в файловое хранилище и связи между ними, плюс кэш файлов. Это сервер обновлений.

Имеется к примеру 5 запросов за 10мс, инди сервер создает 5 потоков, один из них захватывает CrititcalSection и работает с пулом - остальные четыре будут ждать. Далее, второй работает - еще три ждут, и т.д. Сейчас так и реализовано. 

Очередь - std::deque в основном потоке, в ней задачи для асинхронной записи в бд в несколько коннектов, о том что к примеру девайс выходил на связь, имеет номер такой-то прошивки и получил задачу номер такой-то. Потоки запросов после отправки response оставляют в очереди запись. Так как к очереди могут обратиться несколько потоков - она тоже должна быть в критической секции.

Ссылка на комментарий
  • 0
4 часа назад, Камышев Александр сказал:

Имеется к примеру 5 запросов за 10мс, инди сервер создает 5 потоков, один из них захватывает CrititcalSection и работает с пулом - остальные четыре будут ждать ...  Сейчас так и реализовано.

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

4 часа назад, Камышев Александр сказал:

Очередь - std::deque в основном потоке, в ней задачи для асинхронной записи в бд...  Так как к очереди могут обратиться несколько потоков - она тоже должна быть в критической секции.

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

Ссылка на комментарий
  • 0
12 часов назад, GASCHE сказал:

Не понятно что тут не изящного

паралич перфекциониста, есть такое понятие :) 

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

Ссылка на комментарий
  • 0
3 часа назад, Камышев Александр сказал:

хотелось бы обрабатывать несколько тысяч запросов в секунду,

На Indy, которые генерят на каждый запрос отдельный поток? Хммм....Несколько тысяч потоков, инициализация каждого - затратная операция с т.з. ОС... ну да, ну да...

Ссылка на комментарий
  • 0
3 часа назад, Камышев Александр сказал:

хотелось бы обрабатывать несколько тысяч запросов в секунду

Ну не знаю, все зависит от времени сколько вы тратите на обработку одного запроса "дядя БИЛ" не рассчитывал на такое количество переключений между потоками в своей "многопоточной" системе даже получение интервала в 1мс с приемлемой погрешностью довольно проблематично.

Ссылка на комментарий
  • 0
7 минут назад, GASCHE сказал:

"дядя БИЛ" не рассчитывал на такое количество переключений между потоками в своей "многопоточной" системе

Поэтому нужно использовать или асинхронную работу, или пул потоков. Я в последнее время остановился на последнем. Благо, MS имеет великолепную реализацию для http.

Ссылка на комментарий
  • 0
41 минуту назад, kami сказал:

Поэтому нужно использовать или асинхронную работу, или пул потоков. Я в последнее время остановился на последнем. Благо, MS имеет великолепную реализацию для http.

вот, отсюда поподробней, как реализовать пул потоков?

indy tcp и http серверы не имеют свойства ServerType как у старого TServerSocket и метода OnGetThread

у embarcadero кроме indy больше ничего и нет по tcp

что ж мне врукопашную создавать слушающий сокет и т.п.? пора видимо соскакивать на qt

Изменено пользователем Камышев Александр
Ссылка на комментарий
  • 0

MS - это Microsoft.

http.sys и великолепная надстройка над ним в виде THttpApiServer из состава mORMot. Естественно - заточено только под Windows.

Ссылка на комментарий
  • 0
1 минуту назад, kami сказал:

Так вам http или tcp?

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

Ссылка на комментарий
  • 0
Только что, Камышев Александр сказал:

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

Так рассуждая, можно вообще до низов дойти :)

Просто если у вас http - то я на текущий момент ничего лучше чем mORMot-ы не нашел. Да и не искал, если честно. Стабильность работы - 101%, в отличие от Indy и Synapse (до этого работал только с ними).

А вот если TCP (своя надстройка над ним), то тут уже есть варианты. Но индейцы все равно в пролете, как и ScktComp.pas в режиме stBlocking.

Всё вышесказанное - imho.

Ссылка на комментарий
  • 0
15 минут назад, kami сказал:

http.sys и великолепная надстройка над ним в виде THttpApiServer из состава mORMot. Естественно - заточено только под Windows.

THttpApiServer kernel-mode server от мелко-мягких, надо будет почитать, спасибо

Ссылка на комментарий
  • 0

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

Ссылка на комментарий
  • 0
58 минут назад, Евгений Корепов сказал:

Я бы исключил из этой схемы "Пул наборов данных в памяти"

а где было сказано, что нужно держать несколько копий наборов данных?

Ссылка на комментарий
  • 0
В 23.09.2016 в 14:07, kami сказал:

а где было сказано, что нужно держать несколько копий наборов данных?

О чем вы? Перечитал свой сообщение, не нашел ничего о "несколько копий наборов данных".

Ссылка на комментарий
  • 0

Среди прочего дошли руки до проекта этой темы.

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

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

Ссылка на комментарий
  • 0
4 минуты назад, Камышев Александр сказал:

и какой ему не хватает памяти?

Видимо оперативной )

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

Ссылка на комментарий
  • 0

последняя строка перед исключением ведет сюда

constructor TThread.Create(CreateSuspended: Boolean);
{$IFDEF POSIX}
var
  ErrCode: Integer;
{$ENDIF POSIX}
begin
  inherited Create;
  FSuspended := not FExternalThread;
  FCreateSuspended := CreateSuspended and not FExternalThread;
  if not FExternalThread then
  begin
{$IF Defined(MSWINDOWS)}
    FHandle := BeginThread(nil, 0, @ThreadProc, Pointer(Self), CREATE_SUSPENDED, FThreadID);
    if FHandle = 0 then
      raise EThread.CreateResFmt(@SThreadCreateError, [SysErrorMessage(GetLastError)]);

далее иду по шагам до строки в IdStack 

procedure TIdStack.RaiseLastSocketError;
-//-
raise EIdSocketError.CreateError(AErr, WSTranslateSocketErrorMsg(AErr));

после чего вылет в AccessViolation и краш процесса.

Разработчики хелп... я не знаю куда дальше копать.

Ссылка на комментарий
  • 0

А почему не посмотреть на готовые решения вроде Redis или Memcached. Мне кажется, они достаточно быстрые. А у редиса даже есть готовые коннекторы.

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

Изменено пользователем Zawuza
Ссылка на комментарий

Присоединяйтесь к обсуждению

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

Гость
Ответить на вопрос...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...