Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[3.135.185.194] |
|
Страницы: (2) [1] 2 все ( Перейти к последнему сообщению ) |
Сообщ.
#1
,
|
|
|
Товарищи, объясните, пожалуйста, можно ли использовать класс TThread для чтения данных объекта из файла (цель - уменьшение времени загрузки, поскольку размер 100-200 Мб) и добавления его в список (глобальная переменная TObjectList) и как.
|
Сообщ.
#2
,
|
|
|
В методе Execute потока нужно выполнить код, занимающийся загрузкой.
По окончани загрузки заблокировать TObjectList от изменения другими потоками (например, с помощью критической секции или даже Synchronize), добавить в него объект, и разлочить. Поток, занимающийся чтением, потратит столько же времени на загрузку, сколько и было, т.е. уменьшения времени загрузки не произойдет. Однако параллельно будет обслуживаться пользовательский интерфейс (программа не "зависнет" на время загрузки) и могут выполняться другие действия. |
Сообщ.
#3
,
|
|
|
Спасибо, MBo, так и получилось:
TLoaderThread = class(TThread) private FMap: TMap; FTag: integer; protected procedure UpdateResults; procedure Execute; override; public constructor Create(Suspended: boolean; ATag: integer); end; и в коде: constructor TLoaderThread.Create(Suspended: Boolean; ATag: Integer); begin inherited Create(Suspended); FTag:= ATag; end; procedure TLoaderThread.UpdateResults; begin BigMap.Add(FMap); end; procedure TLoaderThread.Execute; begin try FMap:= LoadMap(FTag); Synchronize(UpdateResults); except on E: Exception do ShowMessage(E.ClassName+': '+E.Message); end; end; вызов: ... LoaderThread:= TLoaderThread.Create(True, I); LoaderThread.FreeOnTerminate:= true; LoaderThread.Priority:= tpHighest; LoaderThread.Resume; ... По ощущениям - немного медленнее прямой загрузки, зато процесс продолжается и программа не подвисает, только почему-то не закрашиваются полигоны загруженной карты, хотя все вроде выполняется. Может ли поток загрузки не успевать за основным потоком? Это я так думаю потому, что карта загружена, обрисовка объектов по контуру происходит все время и контуры есть, а закраска через тесселяцию (речь об OpenGL) и запекание в текстуру. При прямой загрузке есть все. |
Сообщ.
#4
,
|
|
|
> только почему-то не закрашиваются полигоны загруженной карты
Про это мы ничего пока не знаем |
Сообщ.
#5
,
|
|
|
Цитата mnj @ Может ли поток загрузки не успевать за основным потоком? Может. |
Сообщ.
#6
,
|
|
|
Про полигоны:
на старте при создании формы читаем заголовочную часть (список карт в файле) в MapList, определяем по нему нужную карту и грузим ее функцией LoadMap function LoadMap(Tag: integer): TMap; var F: TStream; I, J, K: integer; B: byte; MapObject: TMapObject; Pt: TMyPoint; begin try try F:= TFileStream.Create('map.cmf', fmOpenRead, fmShareDenyNone); ....................... Map:= TMap.Create; //пустая карта, читаем габаритные границы F.ReadBuffer(Map.PlaneLimits.SW.Lat, SizeOf(Map.PlaneLimits.SW.Lat)); F.ReadBuffer(Map.PlaneLimits.SW.Lon, SizeOf(Map.PlaneLimits.SW.Lon)); ................... читаем разные данные карты и ее объекты .......................... F.ReadBuffer(MapObject.Size.SizeX, SizeOf(MapObject.Size.SizeX)); //ширина ...................................... if (MapObject.Local = LND) or (MapObject.Local = SQR) then //если объект является площадным, то //-------------------------------------------------------------------------------------------- Tesselation(MapObject.Key,MapObject.CoordList); //делаем мозаику из треугольников //-------------------------------------------------------------------------------------------- Map.ListOfObjects.Add(MapObject); //и добавляем в список .......................... Map.Loaded:= True; //карта загружена Result:= Map; except ................ finally FreeAndNil(F); frmPrBar.Close; end; end; В свою очередь //Тесселяция полигонов procedure Tesselation(MapObjectKey: integer; Points: TList); var TessObj: GLUTesselator; I: integer; A: ArrayOfVector; P: TMyPoint; begin TessObj := gluNewTess; gluTessCallback(TessObj, GLU_TESS_VERTEX, @glVertex3dv); gluTessCallback(TessObj, GLU_TESS_BEGIN, @glBegin); gluTessCallback(TessObj, GLU_TESS_END, @glEnd); gluTessCallback(TessObj, GLU_TESS_ERROR, @errorCallback); gluTessCallback(TessObj, GLU_TESS_COMBINE, @combineCallback); gluTessNormal(TessObj,0,0,1); gluTessProperty(TessObj, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_ODD); // SetLength(A, Points.Count); for I := 0 to Points.Count-1 do begin P:= Points.Items[I]; A[I,0]:= (P.Lon-StartLon)/Un; A[I,1]:= (P.Lat-StartLat)/Un; A[I,2]:= 0; end; glGenLists(1); glNewList(MapObjectKey, GL_COMPILE); glShadeModel(GL_FLAT); gluTessBeginPolygon (TessObj, nil); gluTessBeginContour(TessObj); //начали контур for I := 0 to Length(A)-1 do gluTessVertex(TessObj, @A[I], @A[I]); gluTessEndContour(TessObj); //закончили контур gluTessEndPolygon(TessObj); glEndList; gluDeleteTess(TessObj); end; Генерация списка происходит (смотрел в отладчике) Далее отрисовка (вызывается в onpaint формы) procedure TGLForm.DrawMap; .............. //собственно рисование //натягиваем текстуру на прямоугольник glEnable(GL_TEXTURE_2D); CurrentPoint.Lon:= -cX; //точка ВС CurrentPoint.Lat:= -cY; for I := 0 to 8 do begin glBindTexture(GL_TEXTURE_2D, TexId[I]); MFrame:= Frames[I]; //квадрат для текстуры из массива ......................... then begin RenderTexture(I); //рендер в текстуру MFrame.Rendered:= True; end; MFrame.Show(MFrame.Limits); end ................................. DrawMessage; end; Все так же выполняется, как и в прямой загрузке Рендер вызывает процедуру рисования каждого объекта (по списку) procedure TGLForm.DrawObject(MapObject: TMapObject); var I, J: integer; Stop: boolean; Color: TVector; Pt: TMyPoint; aVertex: array of array of GLFloat; begin I:= 0; Stop:= False; repeat if MapObject.Class_code = FilterObjects[I].Code then begin if (MapObject.Local = SQR) or (MapObject.Local = LND) then begin glEnable(GL_BLEND); glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) ; Color:= ColorToRGB(FilterObjects[I].Color); glColor4f(Color[0],Color[1],Color[2], FilterObjects[I].Transparency); glCallList(MapObject.Key); И список тоже вызывается и значение ключа (номер списка) тоже уникальное. Все проверено в отладчике. При прямом вызове (при начальной загрузке) все работает, если вызывать прямо при необходимости - тоже работает, останавливая выполнение программы на время подгрузки, через thread - все команды проходят, даже до появления заметных лысин в надвигающейся карте, но... но результат выглядит как будто список glCallList не вызывается при загрузке через thread: скр1 сделан с закомментаренной командой glCallList, а скр2 с работающей (из-за большого размера я положил их к себе https://yadi.sk/d/5IyViG3T3FEzdm). Решения я пока не вижу . |
Сообщ.
#7
,
|
|
|
Быть может (у меня так было), что надо создавать их (gl-списки) в разных контекстах (wglCreateContext), а потом (ввиду разных потоков) рассказать, что это всё своё - wglShareLists(...).
|
Сообщ.
#8
,
|
|
|
Славян, можно поподробнее?
|
Сообщ.
#9
,
|
|
|
Ну схематично где-то так:
1.До запуска потоков надо насоздавать контекстов сколь и потоков. Я под винду фурычу, поэтому вызовами wglCreateContext. 2.В каждом потоке при запуске сказануть, что в своём контексте робим - wglMakeCurrent. Это всё оттого, что openGL не приспособлен был изначально для многопоточности. 3.У меня тоже "до" сказано, что списки общие: hRC2 = wglCreateContext( hDC ); wglShareLists( hRC, hRC2); |
Сообщ.
#10
,
|
|
|
Спасибо, Славян, именно так я и сделал (в 2 часа ночи ). Нашел на https://www.khronos.org/opengl/wiki/OpenGL_..._multithreading. Работает. И еще раз спасибо MBo
|
Сообщ.
#11
,
|
|
|
Поток - странная штука: если грузить в основном потоке, рисует правильно, если в параллельном - появляются какие-то необъяснимые артефакты, при том, что для загрузки используется одна и та же процедура. Может, кто подскажет, в какую сторону копать? (картинки лежат https://yadi.sk/d/8MG8y5XR3FZRZn)
|
Сообщ.
#12
,
|
|
|
Залезу в чужую тему чтобы не создавать новую похожую
Задача похожа на топикстартерную - есть основной поток и есть потоки, занимающиеся чтением из файлов данных, декодированием их и складыванием в буфер. В чём проблема - класс потока я хотел бы использовать в нескольких похожих по функциям приложениях без какой-либо модификации. Поэтому хотелось бы, чтобы буфер данных создавался и обслуживался самим потоком. А основная программа просто получала бы нужные ей данные из потока. В связи с этим у меня возникли вопросы: 1. Если в в TThread описываю Public процедуру или функцию и вызываю её из основного потока - она выполняется в основном потоке или в потоке обработке данных? 2. Если ответ на 1 - в основном потоке, то надо ли разделять процедуры на вызываемые "изнутри" потока обработки данных и снаружи? То есть возможен ли какой-то конфликт, если некую public процедуру я вызову из основного класса и во время её выполнения её же вызовет поток обработки данных. 3. Данные у меня лежат в TThreadList. Основной поток должен обращаться к потоку обработки данных и получать один объект из листа. Как с ним дальше работать? То есть я лочу лист, получаю доступ к списку объектов, получаю объект, разлочиваю лист. Но сам объект то не залочен. То есть как мне в основном потоке корректно обращаться к объекту, к которому может обратиться и поток обработки данных? Или мне просто копировать объект в локальные данные основного потока и только после этого разлочивать лист? |
Сообщ.
#13
,
|
|
|
>Если в в TThread описываю Public процедуру или функцию и вызываю её из основного потока - она выполняется в основном потоке
В основном - выполняется в контексте вызвавшего потока >То есть возможен ли какой-то конфликт, если некую public процедуру я вызову из основного класса и во время её выполнения её же вызовет поток обработки данных. Конфликта кода не будет, но если процедура использует общие данные, два её экземпляра в разных потоках будут конфликтовать по данным Т.к. > к которому может обратиться и поток обработки данных то проще всего копировать. Но эта схема странноватая. Объект будет изменен и его снимок состояния станет неактуальным. Или так и задумано? |
Сообщ.
#14
,
|
|
|
Цитата MBo @ В основном - выполняется в контексте вызвавшего потока Ага, уже хорошо. То есть все сервисные процедуры можно описать в самом классе потока. Попробую объяснить саму задачу. У меня написан для рабочих целей набор видеоплейеров, которые воспроизводят видеоклипы на видеокарту BlackMagic decklink. От простейшего, который просто крутит клип по кругу, до довольно сложного с плейлистом и выводом на несколько видеокарт параллельно. Воспроизведение идёт бесподрывно, то есть клипы воспроизводятся друг за другом без подрыва в момент перехода, для этого используется библиотека gmfbridge. Всё это работает на DirectShow. К сожалению, после довольно долгого использования у библиотеки обнаружилось немалое количество глюков. Последняя версия библиотеки выпущена лет 10 назад, автор её давно забросил. Разбираться в чужом коде мне совершенно не интересно. Возникла идея переделать всё по принципу фрейм-серверов без использования directshow вообще. То есть каждый видеофайл открывается в своём экземпляре класса с помощью библиотек ffmpeg и класс выдаёт считанные кадры головному процессу, который пересылает их в нужном порядке видеокарте. Соответственно, задача простая - передать в класс имя файла и параметры нужного выходного кадра. Класс создаёт буфер из N кадров, открывает файл и настраивает конвертер. Далее нужно реализовать следующее - головной процесс обращается к классу "дай мне кадр с таким-то таймстемпом". Класс просматривает свой буфер, находит нужный кадр и отдаёт ссылку на него головному процессу. Или говорит - у меня нет такого таймстемпа - подожди. После чего буфер полностью очищается, класс прыгает по файлу на нужную точку и заполняет буфер кадрами, начиная с запрошенного. После чего шлёт сообщение в головной поток - я готов. Класс фреймсервера должен быть отдельным потоком, так как библиотека ffmpeg полностью синхронная и открытие файла может спокойно занимать несколько секунд (пока считаются все заголовки и индексы). А в это время основной процесс должен продолжать нормально работать. Вот эта вот процедура запроса кадра с заданным таймстемпом - единственная, которую нужно вызывать из основного потока. Про неё и был вопрос. И вопрос про вот этот вот "отданный" в основную процедуру кадр. Если мы отдаём непосредственно кадр из буфера потока, то мне нужно будет пометить этот кадр, как принадлежащий основному потоку, чтобы фреймсервер не пытался его повторно использовать. Достаточно ли для этого сделать boolean поле locked, например. И при отдаче кадра основному потоку ставить его в true, а когда он больше не нужен основному потоку - ставить его в false? Иди лучше всё-таки при запросе кадра копировать его в новый буфер и отдавать этот буфер основной программе, а программа уже сама его очистит когда не надо? Тут я просто боюсь проблемы фрагментации памяти - каждый кадр будет примерно по 4 МБ и 25 раз в секунду просить от системы по 4МБ и тут же их отдавать - не будет ли проблем. |
Сообщ.
#15
,
|
|
|
Цитата An_private @ 1. Если в в TThread описываю Public процедуру или функцию и вызываю её из основного потока - она выполняется в основном потоке или в потоке обработке данных? В том из которого вызвали. Без каких либо исключений. Цитата An_private @ 2. Если ответ на 1 - в основном потоке, то надо ли разделять процедуры на вызываемые "изнутри" потока обработки данных и снаружи? То есть возможен ли какой-то конфликт, если некую public процедуру я вызову из основного класса и во время её выполнения её же вызовет поток обработки данных. Где есть общие данные, то там неприменимо возникает конфликт. Да, нужна синхронизация. А ещё лучше модель взаимодействия потоков. Цитата An_private @ Данные у меня лежат в TThreadList. Если у каждого потока будет своя корпия данных, то никаких конфликтов соответственно не будет. Но я бы данные удалил из списка. И только после этого снял Lock. Цитата An_private @ Ага, уже хорошо. То есть все сервисные процедуры можно описать в самом классе потока. Ну можно, только это решение через одно место. Вам придётся в каждом публичном методе сделать синхронизацию, а именно вставить критическую секцию. А там не далеко до проблем известных как гонки потоков и DeadLock. Цитата An_private @ так как библиотека ffmpeg полностью синхронная А она потока безопасна? Цитата An_private @ Достаточно ли для этого сделать boolean поле locked, например. И при отдаче кадра основному потоку ставить его в true, а когда он больше не нужен основному потоку - ставить его в false? Не совсем. Не просто Boolean, а мьютекс. Цитата An_private @ Иди лучше всё-таки при запросе кадра копировать его в новый буфер и отдавать этот буфер основной программе, а программа уже сама его очистит когда не надо? Тут я просто боюсь проблемы фрагментации памяти - каждый кадр будет примерно по 4 МБ и 25 раз в секунду просить от системы по 4МБ и тут же их отдавать - не будет ли проблем. Эта неважно с вероятностью 90% вам в любом случае придётся писать свой менеджер памяти. Лучше просто раскидать не по потокам, а по процессам. |