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

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

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


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

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

  • 0

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

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

Скорее всего, в ProcessExplorer вы увидите, что ваш процесс "сожрал" гектара полтора в PrivateBytes.

 

Собственно, об этом я раньше уже писал. В этой теме.

Изменено пользователем kami

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


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

Скорее всего, в ProcessExplorer вы увидите, что ваш процесс "сожрал" гектара полтора в PrivateBytes.

в процессах пиковая память 80м - ни о чём.

http://www.transl-gunsmoker.ru/2010/09/windows-2000.html тут пишут что в принципе больше 2k  средов один процесс не может сделать.

проблема не в этом, винда при вызове API вернула ошибку - ок обработали, работаем дальше, так нет в ошибку доступа и крашиться процесс, это беда библиотеки

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


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

Ограничте количество соединений самостоятельно разумной цифрой, при попытке соединения сверх лимита отдавайте в хеадере редирект на следующий сервер в кластере. Если конечно есть следующий сервер...

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


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

Ограничте количество соединений самостоятельно разумной цифрой, при попытке соединения сверх лимита отдавайте в хеадере редирект на следующий сервер в кластере. Если конечно есть следующий сервер...

инди создает срэд перед вызовом OnCommandGet, есть HeadersAvailable но оттуда редиректнуть нельзя, а смысл перенаправлять если ресурсы уже заняты...

MaxConnections есть ограничение в IdHTTTPServer, и да, разумная цифра 1024 там не будет лишней.

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


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

в процессах пиковая память 80м - ни о чём.

Я бы не стал доверять стандартному диспетчеру задач. Не знаю, что означает "пиковая память", но скорее всего - это максимальное значение памяти, которое процесс занимал в физической оперативке. Это совсем не соответствует тому, сколько процесс реально взял памяти у системы.

32 разрядному процессу доступно в теории 4Гб. На практике - больше 2Гб (а в подавляющем большинстве случаев - 1,5Гб) получить не удается.

9 минут назад, Камышев Александр сказал:

ок обработали, работаем дальше,

Пардон, а как работать дальше? На каждый чих, на почти каждую строчку кода требуется выделение памяти. Где ее взять, если она закончилась?

Правильно процесс вылетает.

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


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

Я бы не стал доверять стандартному диспетчеру задач. Не знаю, что означает "пиковая память", но скорее всего - это максимальное значение памяти, которое процесс занимал в физической оперативке. Это совсем не соответствует тому, сколько процесс реально взял памяти у системы.

означает "Пиковый рабочий набор", Process Explorer показал Private Bytes 100м и Working Set 63м.

Может я не туда смотрю... где гектары?

Полагаю ограничение все-таки размер стэка, т.е. размер резервируемых адресов там превышен.

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

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


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

инди создает срэд перед вызовом OnCommandGet, есть HeadersAvailable но оттуда редиректнуть нельзя, а смысл перенаправлять если ресурсы уже заняты...

MaxConnections есть ограничение в IdHTTTPServer, и да, разумная цифра 1024 там не будет лишней.

Смысл в том что в комбинации с IdHTTPServer1.KeepAlive:=False, код ниже будет сразу освобождать ресурсы, т.е. количество соединений не превысит 1024 плюс несколько десятков отправленных в редирект с последующим отключением.

procedure TForm1.IdHTTPServer1CommandGet(AContext: TIdContext;
  ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
  if CurrentConnectionCount>1024 then
    AResponseInfo.Redirect('sadas.asdasf.com');
end;

 

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


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

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

Еще есть LockFree концепция, которая сводит почти к минимому использование блокировок. Главная идея - это использование атомарных функций и специальных флагов помечающих актуальность данных.

Zawuza понравилось это

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


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

Вы можете попробовать втиснуть больше потоков в ваш процесс уменьшением начального размера стека - вы можете сделать это либо указанием размера в заголовке PE-файле, либо вручную указав размер стека в функции CreateThread, как указано в MSDN:
1
h := CreateThread(nil, 4096, @ThreadProc, nil, STACK_SIZE_PARAM_IS_A_RESERVATION, id);

Пытаюсь изменить код в исходниках System.Classes.pas:

constructor TThread.Create(CreateSuspended: Boolean);
-//-
{$IF Defined(MSWINDOWS)}
  
//#define STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000
//#define CREATE_SUSPENDED                  0x00000004
    
	//FHandle := BeginThread(nil, 0, @ThreadProc, Pointer(Self), CREATE_SUSPENDED, FThreadID);
    FHandle := BeginThread(nil, 65536, @ThreadProc, Pointer(Self), $00010004, FThreadID); // изменения тут

    if FHandle = 0 then
      raise EThread.CreateResFmt(@SThreadCreateError, [SysErrorMessage(GetLastError)]);

компилируется, создаю System.Classes.hpp, однако нет эффекта совсем,

удалял совсем стоку создания потока BeginThread - никакого эффекта, видимо rtl не так просто изменить.

как применить изменения?

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


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

:D заработало:D

3600 запросов в секунду, пока не падает, в клиенте время запроса 0,7 секунды поднялось с 0,1 и 70% процессорного времени

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

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

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


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

:D заработало:D

Хоть пример выложи, интересно же )

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


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

void __fastcall TdmSkyUpdate::serverCommandGet(TIdContext *AContext,
				TIdHTTPRequestInfo *ARequestInfo, TIdHTTPResponseInfo *AResponseInfo)
{
strCommandData cd;
#ifdef AUX_MODE
	#ifdef MY_DEBUG_MODE
		cd.log_queue.push_back( ARequestInfo->RawHTTPCommand ); // debug
		cd.log_queue.push_back( ARequestInfo->RawHeaders->Text ); // debug
	#endif
	MyTimer timer; timer.Start();
#endif
if ( db_queue_size >= max_db_queue_size )
	{
		AResponseInfo->ResponseNo = 503;
		#ifdef MY_DEBUG_MODE
			cd.log_queue.push_back( "Переполнение очереди в бд, ограничение "
				+ (AnsiString)max_db_queue_size ); // debug
			PushToLogQueue( &cd.log_queue );
			TIdNotify::NotifyMethod( OnLogQueue );
		#endif
		return;
	}
cd.req = ARequestInfo; cd.resp = AResponseInfo;
cd.user = cd.req->AuthUsername; cd.pass = cd.req->AuthPassword;
AResponseInfo->CloseConnection = ARequestInfo->Connection.LowerCase() != "keep-alive";
switch ( ARequestInfo->CommandType )
	{
		case hcGET: GetCommand( &cd ); break;
		case hcPOST: PostCommand( &cd ); break;
		case hcHEAD: HeadCommand( &cd ); break;
		case hcPUT: break;
	}
#ifdef MY_DEBUG_MODE
PushToLogQueue( &cd.log_queue );
#endif
#ifdef AUX_MODE
	int req_time = timer.GetTimeMSec();
	if ( stat.dev_req_time < req_time ) Interlocked->Exchange( stat.dev_req_time, req_time );
#endif
}
//---------------------------------------------------------------------------

 

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

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


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

void TdmSkyUpdate::PostCommand( strCommandData *cd )
{
bool is_client = false;
if ( !cd->req->AuthExists )
	{
		cd->sx_auth = cd->req->RawHeaders->Values["X-SX-auth"];
		if ( cd->sx_auth.IsEmpty() ) { cd->resp->ResponseNo = 401; return; }
		is_client = true;
	}
if ( !is_client )
	{
		strDeviceCmd dc; dc.cd = cd;
		cs_pool->Enter();
		PostDevice( &dc );
		cs_pool->Leave();
		if ( !dc.db_queue.size() ) return;
		if ( !PushToDBQueue( &dc.db_queue ) ) { cd->resp->ResponseNo = 500; return; }
	}
else
	{
		strClientCmd cc; cc.cd = cd;
		cs_pool->Enter();
		PostClient( &cc );
		cs_pool->Leave();
		if ( !cc.db_queue.size() ) return;
		if ( !PushToDBQueue( &cc.db_queue ) ) { cd->resp->ResponseNo = 500; return; }
		if ( cc.file_data.data ) SaveTaskFile( &cc.file_data, cc.file_id );
	}
}
//---------------------------------------------------------------------------

 

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


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

думаю эти два куска показывают основную обработку OnCommandGet,

из хедера:

typedef std::deque< strDBQueueMember* > db_queue_deque;
db_queue_deque db_queue;
TCriticalSection *cs_pool, *cs_queue, *cs_files;
TInterlocked *Interlocked;

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

ну и вот это обязательно в System.Classes.pas  в Embarcadero\Studio\17.0\source\rtl\common\

constructor TThread.Create(CreateSuspended: Boolean);
-//-
{$IF Defined(MSWINDOWS)}
  
//#define STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000 - это для информации
//#define CREATE_SUSPENDED                  0x00000004 - это для информации
    // заменить  FHandle := BeginThread(nil, 0, @ThreadProc, Pointer(Self), CREATE_SUSPENDED, FThreadID);
    // на
	FHandle := BeginThread(nil, 65536, @ThreadProc, Pointer(Self), $00010004, FThreadID);

System.Classes.pas добавить в проект.

и будет вам счастье :)

Изменено пользователем Камышев Александр
Kitty, Евгений Корепов и Rusland понравилось это

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


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

А если  TIdHTTPServer заменить на TIdTCPServer, интересно на сколько увеличится быстродействие? И еще вопрос - по ядрам процессора нагрузка распределяется равномерно или грузит только одно ядро в вашем случае?

 

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


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

А если  TIdHTTPServer заменить на TIdTCPServer, интересно на сколько увеличится быстродействие? И еще вопрос - по ядрам процессора нагрузка распределяется равномерно или грузит только одно ядро в вашем случае?

 

Не думаю что сколько нибудь заметно, если брать http пакеты, ведь TIdHTTPServer наследник от TIdCustomTCPServer. Если пересылать бинарные массивы, без парсинга хедеров - возможно и будет небольшое увеличение.

Был приятно удивлен, по всем ядрам ровная нагрузка.

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

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


Ссылка на сообщение
Поделиться на других сайтах
  • 0
В 22.09.2016 в 12:01, kami сказал:

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

результаты краш-теста - упал в "Недостаточно памяти" после 5770 запросов в секунду, как бы и ничего так :) 

С обращением к пулу - 3к и без задержек на доступ к критическим секциям 5к запросов с задержкой 200 мсек. - непринужденно, конечно после допила системного TThread, беда кстати была не в инди, а в теплом ламповом borland.

тестировалось на ПК разработчика, сервер и эмулятор на localhost:

2017-01-13_15-58-44.png

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

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


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

Как-то вы поступили очень грубо :)

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

http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/devcommon/compdirsmemoryallocsizes_xml.html

 

type
  TTh = class(TThread)
  protected
    procedure Execute; override;
  end;

implementation

procedure Do;
var
  I: Integer;
begin
  for I := 1 to 5000 do
    TTh.Create(False);
end;

{ TTh }

{$M 16384, 65535}
procedure TTh.Execute;
begin
  while True do
    Sleep(50);
end;

 

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

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


Ссылка на сообщение
Поделиться на других сайтах
  • 0
В 13.01.2017 в 23:59, egorea1999 сказал:

 

Как-то вы поступили очень грубо :)

 

Бывает B)

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

The memory allocation directives are meaningful only in a program. They should not be used in a library or a unit.

т.е. в System.Classes.pas и в IdThread.pas вставлять нельзя, ибо should not be used in a library or a unit.:( Indy сама создает потоки в коде библиотеки IdCustomTCPServer.pas.

и еще {$M 16384, 65535} - полагаю размеры указаны неверно, от 64К до 1М, возможно я ошибаюсь, вроде: гранулярность выделения адресного пространства равна 64 Кб, сдругой стороны из описания

Default  
{$M 16384,1048576}  

 

В общем, все таки брутальный подход, только хардкор...

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

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


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

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

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

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

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


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

Войти

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


Войти сейчас

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

    • Автор: 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 пользователей

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