• 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. Как влияет количество ядер, процессоров, на быстродействие во втором и третьем случае?

Rusland и Pax Beach понравилось это

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

44 ответа на этот вопрос

  • 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
42 минуты назад, kami сказал:

Благо, MS имеет великолепную реализацию для http.

MS? - это куда?

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
  • 0

MS - это Microsoft.

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

Камышев Александр понравилось это

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
  • 0
1 час назад, Камышев Александр сказал:

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
  • 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

ок, как обработать чтобы не вис?

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
  • 0

в мониторе ресурсов - Анализ цепочки ожидания, там пишет "оказался в ситуации взаимоблокировки"

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
  • 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

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Создайте аккаунт или войдите для комментирования

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

Создать аккаунт

Зарегистрируйтесь для получения аккаунта. Это просто!


Зарегистрировать аккаунт

Войти

Уже зарегистрированы? Войдите здесь.


Войти сейчас

  • Похожие публикации

    • Автор: david_yusupov
      Вопрос очень прост, как убить запущенный поток не дожидаясь его завершение.
      Пример:
      procedure TBufferThread.Execute;
      begin
          while not Self.Terminated do
          begin
                Sleep(10000);
          end;
      end;
       
      Используя ReportMemoryLeaksOnShutdown показывает, что поток жив, после закрытие программы?
      PS
      Поток так же жив после вызова процедуры   TThread.Terminate (После того как заглянул вовнутрь понял почему, и поэтому возник вопрос, как убить поток?)
    • Автор: rareMax
      ПРивет. Хотел спросить - можно ли ставить на прослушивание TidHTTPServer  нужный мне адресс? Или только ИП локального компьютера можно ставить? Когда ставлю не локальный ИП (напр. 87.242.70.105:80) - то сервер не может запуститься. 
      Ошибка такая:
      Пробовал смотреть активные соединения через команду винды (netstat) - там такой ИП не кем не используется.
      Спасибо за любые наводки
    • Автор: 97mik
      Как сделать так, чтобы во время выполнения долговременных операций у меня не "замораживался" интерфейс приложения? 
       
      Например, я выполняю по сети скачивания большого файла и на время скачивания отображаю индикатор загрузки. Но пока файл полностью не загрузиться индикатор не работает.
    • Автор: Tarik02
      Я загружаю картинку в отдельном потоке из сервера(пока из localhost). Иногда бывает, картинка загружается, иногда - нет. Если закрыть программу, выскакивает ошибка:

    • Автор: kidrock1
      Всем привет, друзья. Столкнулся со следующей проблемой. Создаётся поток с целью загрузки картинок во время чтения статьи. Да так чтобы основная форма в момент загрузки не была заморожена. Вот код:
      type ThreadHTTP1=class(TThread) private i:integer; public procedure Execute;override; procedure ShowResult1; end; procedure ThreadHTTP1.Execute; var j:integer; begin inherited; try lStream1 := TMemoryStream.Create; Form3.IdHTTP1.Get('http://totalmma.ru/newsupload/8306.jpg',lStream1); except // ShowMessage('no'); end; Synchronize(ShowResult1); end; procedure ThreadHTTP1.ShowResult1; begin ShowMessage(IntToStr(Form3.IDHTTP1.Response.ContentLength)); Form3.Image1.Bitmap.LoadFromStream(lStream1); Form3.Image1.Visible := true; Form3.Caption:=IntToStr(i)+' kbs'; Form3.IdHTTP1.Free; Stream.Free; end; procedure TForm3.Button1Click(Sender: TObject); var MyHTTP1:ThreadHTTP1; begin MyHTTP1 := ThreadHTTP1.Create(False); end; Так вот проблема в следующем. На винде всё работает. А вот когда компилируешь под андройд и переносишь на телефон, при нажатии на кнопке ничего не происходит. Очень прошу помочь, друзья.
    • Автор: Kikoma
      Следующая ситуация:
       
      Есть база данных с полями: id, product_name, price, cart (корзина) - Думаю пояснять излишне.
       
      по условию cart>0 формируется запрос и заполняется TListBox кастомизированным Item-ом который содержит SpinBox. при изменении SpinBox вызывается процедура, которая вносит изменение в БД (cart) и высчитывает сумму в этом Item.
       
      Все работает, все хорошо, но...
      Задумал я что при SpinBox = 0, у меня этот TListBoxItem исчезал, для этого на изменение SpinBox если он равен 0, я запускаю процедуру формирования (Заполнения) этого TListBox заново.
       
      Вываливается ошибка Access ..to address XXX, при чем при пошаговой трассировки исключение вызывает FMX.Edit строка 3811 CustomEditBox.Change; в procedure TValueRangeCustomEditBox.DoAfterChange; (DELPHI XE6)
       
      т.е. моя процедура полностью отрабатывается (Заполняется новый список Item-ов) и возникает эта ошибка.
       
      При чем на 32-bit Windows все работает нормально, только на андроиде возникает это исключение, при чем приложение продолжает нормально функционировать.
       
      Если я правильно понимаю, то эта процедура DoAfterChange пытается что то сделать с объектом, которого уже нет.
       
      Это баг или я неправильно алгоритм построил?
       
       
    • Автор: estra
      Хочу сделать переключение TabItem'ов с 3D эффектом (Красивое 3D переключение вкладок в TabControl). Ну устройстве наблюдаются сильные тормоза при анимации, по-этому хочу запускать в отдельном потоке, но получаю ошибку ALOOPER_POLL_ERROR. Как исправить?
      TTabControl_3DRotation.zip
    • Автор: brunnengi
      Здравствуйте.
       
      Подскажите пожалуйста как сделать правильно.
      По нажатию на Button происходит запрос к серверу. Сейчас я делаю запрос вызывая отдельный поток. А на время выполнения показываю просто layout на весь экран с вращающимся AniIndicator. Правильно ли вообще делать это в отдельном потоке и если нет, то как правильно, подскажите плз.
      И нормально ли выводить отдельный layout с индикатором, или же стоит использовать какое нибудь системное окошко со значком ожидания? Если системное, то как его вызвать? 
  • Сейчас на странице   0 пользователей

    Нет пользователей, просматривающих эту страницу