Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Основная";
Текущий архив: 2006.10.29;
Скачать: [xml.tar.bz2];

Вниз

Проблемы с выделением памяти в потоках   Найти похожие ветки 

 
Павел И   (2006-09-17 21:08) [0]

Здравствуйте - такой вопрос
при работе с функциями
  GetMem и FreeMem в разных потоках возникает ошибка OutOfMemory

проблема возникла при написании одной задачи, вот пример программы(ничего не делающей) в которой эта проблема проявляется -


unit MainUnit;

interface

uses
 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
 Dialogs, StdCtrls;

type
 TForm1 = class(TForm)
   Button1: TButton;
   procedure Button1Click(Sender: TObject);
 end;

TThread1=class(TThread)
 procedure Execute;override;
end;
TThread2=class(TThread)
 procedure Execute;override;
end;

var
 Form1: TForm1;

implementation

{$R *.dfm}

procedure TThread1.Execute;
var
 p:pointer;
begin
 inherited;
 repeat
  GetMem(p,30*1024*1024);// выделить и освободить
  FreeMem(p);            // 30 мегабайт
 until false;
end;

procedure TThread2.Execute;
const l=3000;
var
 i:integer;
 a:array[1..l] of pointer;
begin
 inherited;
 repeat
  for i:=1 to l do GetMem(a[i],10);// выделить и освободить
  for i:=1 to l do FreeMem(a[i]);  // 3000 раз по 10 байт
 until false;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
Thread1:TThread1;
Thread2:TThread2;
begin
Thread1:=TThread1.Create(false);// Создать и запустить 1 поток
Thread2:=TThread2.Create(false);// Создать и запустить 2 поток
end;

end.


при запуске потоков по отдельности в процедуре Button1Click все работет отлично, но при одновременном их запуске возникает ошибка OutOfMemory.
Подскажите пожалуйста что неправильно.

Спасибо


 
Dmitrij_K   (2006-09-17 21:17) [1]

А сам догадаться не можешь?
OutOfMemory


 
Павел И   (2006-09-17 21:26) [2]


> А сам догадаться не можешь?
> OutOfMemory
>


Памяти запрашивается очень мало всего 30 мб в первом потоке и 30 кб во втором, после выделения она сразу освобождается, как может быть что ее действительно недостаточно? (Тем более что можно свободно взять и 300 мб, проблема возникает только при использовании нескольких потоков)


 
Dmitrij_K   (2006-09-17 21:33) [3]


> осле выделения она сразу освобождается,

Извени, не заметил.
У меня работает нормально.


 
Павел И   (2006-09-17 21:49) [4]


> У меня работает нормально.


Да у меня тоже в этом примере ошибка появляется только при запуске из под Delphi(его отладчика), а в других случаях она появляется и при чистом запуске приложения, есть некоторая зависимость ее появления от размера выделяемой памяти, если в первом потоке выделять 3 мб она будет, а если 10 кб ее нет, но дело точно не в том что памяти действительно нехватает.
При этом если внутренности циклов repeat -- until в каждом потоке поместить в защищенную секцию ошибки не будет, но разве это обязательно для такого кода ?


 
Павел И   (2006-09-17 22:04) [5]

Извиняюсь за неточное выражение под защищенной секцией я имел ввиду TRTLCriticalSection.

При этом если даже написать на функции GetMem и FreeMem обертки с критической секцией, это не помогает, чтобы ошибка не возникала надо помещать внутренности циклов repeat -- until целиком, что несколько странно(может я просто непонимаю чего-то).


 
Korneley ©   (2006-09-17 22:15) [6]

> При этом если внутренности циклов repeat -- until в каждом
> потоке поместить в защищенную секцию ошибки не будет, но
> разве это обязательно для такого кода ?

 Вообще-то я всегда считал, что если GetMem, или там .Create какой, то далее try...finally ... FreeMem / Free ... end - это норма. А вот на подобные грабли я наезжал, точно не понял в чем дело, но мне кажется все дело в дельфийском менеджере памяти и конфигурацией виндов (сервис-паки, MSIE и т.д.). Потому, что некоторые приложения ведут себя по разному из-под отладчика и вне его. Пока борюсь просто - переписываю код в различных комбинациях пока не заработает :)


 
Loginov Dmitry ©   (2006-09-17 22:21) [7]

Тестировал код минут 10 (забирал памяти в 10 раз больше). Дельфя и винда таже, что и в сабже. Никаких намеков на ошибки.


 
Kolan ©   (2006-09-17 22:33) [8]

А сколько памяти на машине?


 
SPACE!!   (2006-09-17 23:46) [9]

>> Dmitrij_K  :))
Действительно OutOfMemory правда виртуальной памяти
надеюсь ты знаешь в чем отличия виртуальной памяти от физической если
нет то поиищи книгу Джффри Рихтера "Создание эффективных WIN32-приложений" так-же есть такой автор Petzold книга извини непомню как
называется что-то типа "программирование в windows 95"  и первое и второе относится к must have.
Так ладно что касается твоей проблемы :
Виртуальное адрессное пространство в 98 и XP имеет размер 2 GB !!! В XP
должна быть возможность увеличения этого пространства до 3 GB .
Виртуальная память выделяется регинами размер которых кратен страницам.
Страница это единица объема памяти. Размер страницы зависит от      
процесора и от OS . В Windows 98 как помоему и в XP на x86 страница  
имеет размер 4кб. Поэтому попросив систему выделить тебе 9кб система
выделет регион размером 12кб. Еще кстате есть гранулярность которое
равна для x86 64кб это значит что каждый новый регион должен  
распологаться по адрессу кратному 64 .

Теперь смотри что приблизительно получается

 поток 1
 цикл 1.
  GetMem(10 байт мне);
  память регион 1 размер округлен до размера страницы 4096 байт :
   1 ****
   2
   3
   4
   
* звездачками заполнена  вирт. память спроецируемая на физическую в
твоем случаи 10 байт остальные 4086 байт пока не используются.


 поток 2
 цикл 1.
  GetMem(10 байт мне);
  память регион 2 размер округлен до размера страницы 4096 байт :
   5 ****
   6
   7
   8


Второй регион располагается сразу за первым и не дает ему расширяться больше 4кб . Я конешно могу ошибаться, но помоему все происходит именно
так. Этот эфект называется фрагментацией. Кстате многие серверные приложения падают после недели непрерывной работы как-раз из-за фрагментации адрессного пространства тоесть к примеру выделяется 4 региона которые идут друг за другом 1 и 3 освобождаются и когда пытаешься выделить пятый размер которого уже больше чем был у 1 и 3
то получаешь OutOfMemory  :) .

Решение функция VirtualAlloc и VirtualFree.

VirtualAlloc позволяет резирвировать регион и потом проецировать адрессное пространство этого региона на физическую память. Тоесть резирвируешь 30 метров и по мере надобности проецируешь.
Есть также возможность резервировать регион с одновременной передачей физической памяти. И еще раз найди книгу Джффри Рихтера там все это
есть и очень подробно , также там есть решения для работы с большими объемами данных и эфективным использованием памяти.

Ну а если на это все нет времени то можно просто синхронизировать потоки
тоесть пока один работает с памятью второй ждет и наоборот если устраивает такой вариант могу показать. Кстате опять же все это есть в той же книге - просто Must Have а не книга . :)


 
SPACE!!   (2006-09-17 23:50) [10]

Используй вместо CriticalSection объект события .


 
SPACE!!   (2006-09-18 00:49) [11]

Вобщем во так все должно работать :
Инициализируем события
HMemoryEvent :=CreateEvent(0,false,true,nil); //  незабудь это сделать


WaitForSingleObject(HMemoryEvent,INFINITE); // Ждем когда события освободится
repeat
 for i:=1 to l do GetMem(a[i],10);// выделить и освободить
 for i:=1 to l do FreeMem(a[i]);  // 3000 раз по 10 байт
until false;
SetEvent(HMemoryEvent);// Освобожаем события


В destructore CloseEvent(HMemoryEvent) // хотя это необязательно.
Тоже самое и для другого потока . Этот код обязан работать , но  я  не проверял ..

Да и попробуй сделать вот так - если конешно это приемлемо для твоей задачи.

type
 PIntegerRec = ^TIntegerRec;
 TIntegerRec = Record
  data  :  byte;
end;

var
IntegerRec : array of PIntegerRec;

for i := 0 to counter do
IntegerRec[i] := new(PIntegerRec);
for i := 0 to counter do
Dispose(IntegerRec[i]);


 
Dmitrij_K   (2006-09-18 09:00) [12]


SPACE!!   (17.09.06 23:46) [9]
>> Dmitrij_K  :))
> Действительно OutOfMemory правда виртуальной памятинадеюсь
> ты знаешь в чем отличия виртуальной памяти от физической
> если

Представь себе, что знаю.

>Павел И   (17.09.06 21:49) [4]
> При этом если внутренности циклов repeat -- until в каждом
> потоке поместить в защищенную секцию ошибки не будет, но
> разве это обязательно для такого кода ?

Обращение к менеджеру памяти уже под зашитой крит секции. И лишнее городить не надо


 
SPACE!!   (2006-09-18 14:20) [13]


> Представь себе, что знаю.

:) Кстате Petzold очень дополняет Рихтера в этом вопросе причем очень
и очень подробно . Он дает четкое представление как работает OS Win с памятью.

> Обращение к менеджеру памяти уже под зашитой крит секции.
>  И лишнее городить не надо

Скорей всего именно так, но это неменяет ситуацию описаную мной выше,если конешно я прав надеюсь кто-то
меня дополнит или поправит в этом вопросе...

Чтоб код нормально работал цикл Repeat должен быть полностью защищен.

>>Павел И
Насчет последнего моего поста извини невнемателен я думал CriticalSection тебе не помогли, поэтому предложил использовать события. Что касается
примера с массивом то здесь тоже забыл упомянуть о процедуре SetLength(). Попробуй использовать этот массив во втором потоке. Хотя скорей всего
результат будет тем-же.

Да и еще а какая у тебя стоит задача может есть более элегантный выход ?


 
SPACE!!   (2006-09-18 16:22) [14]

>>Павел И
Так вариант с New не пройдет он использует getmem кстате глянь исходники : \DelphiPath\Source\Rtl\Sys\getmem.inc

Кстате сделай вот так после второй итерации :

ShowMessage("a[0] ="+inttostr(integer(a[0]))+#13+"a[1] ="+inttostr(integer(a[1])) );

я думаю это подтверждает мою теорию даные a[1] идут сразуже за a[0] .


 
SPACE!!   (2006-09-18 16:30) [15]

Я думаю еще стоит попробовать сделать вот так :

 GetMem(p,30*1024*1024);// выделить и освободить
 FreeMem(p,30*1024*1024);            // 30 мегабайт


 
Dmitrij_K   (2006-09-18 16:33) [16]

Попробуй вопрос на vingrad ru задать


 
Leonid Troyanovsky ©   (2006-09-18 19:13) [17]


> Dmitrij_K   (18.09.06 16:33) [16]

> Попробуй вопрос на vingrad ru задать


Злостный PR, IMHO.

--
Regards, LVT.


 
sniknik ©   (2006-09-18 21:16) [18]

по моему она (память) просто не успевает освобождаться... операция то ресурсоемкая. попробуй в обоих циклах в конце sleep(10) например поставь и проверь.


 
Павел И   (2006-09-18 22:06) [19]

Всем большое спасибо за ответы.


> Kolan ©   (17.09.06 22:33) [8]
>
> А сколько памяти на машине?
>


На машине 1 GB памяти.


> SPACE!!   (17.09.06 23:46) [9]
> Этот эфект называется фрагментацией. Кстате многие серверные
> приложения падают после недели непрерывной работы как-раз
> из-за фрагментации адрессного пространства


Спасибо за развернутый ответ, проблема вероятно именно в этом, в приложении, в котором стала возникать эта ошибка, она возникает через несколько часов, причем в нем много работы ведется со множеством небольших выделенных участков памяти, в несколько потоков.
Если все загородить критическими секциями, то фактически будет рабочим только один поток, кроме того это не даст гарантии, что эта ошибка никогда не появится при длительной работе(приложение весьма сложное).


> SPACE!!   (18.09.06 16:30) [15]
>
> Я думаю еще стоит попробовать сделать вот так :
>
>  GetMem(p,30*1024*1024);// выделить и освободить
>  FreeMem(p,30*1024*1024);            // 30 мегабайт
>


И при таком варианте ошибка возникает.


> SPACE!!   (18.09.06 16:22) [14]
>
> Кстате сделай вот так после второй итерации :
>
> ShowMessage("a[0] ="+inttostr(integer(a[0]))+#13+"a[1] ="+inttostr(integer(a[1]))
> );
>
> я думаю это подтверждает мою теорию даные a[1] идут сразуже
> за a[0] .
>


Да, адрес a[0] - 299958280 , a[1] - 299958296 .

Попробую добавить критические секции на некоторые участки пока не разбирусь с этой проблемой подробно(найду книгу Джффри Рихтера и попробую избежать использования черезмерного количества малых участков памяти).


 
SPACE!!   (2006-09-18 23:45) [20]


> sniknik
> по моему она (память) просто не успевает освобождаться..
> . операция то ресурсоемкая. попробуй в обоих циклах в конце
> sleep(10) например поставь и проверь


Действительно проверь , но помоему функция FreeMem должна возвращять управления только после высвобождения , но да все может быть.

Есче раз о моей версии :
 
Виртуальная память до запуска 2 потоков :

____________________________
Регион    Статус      Размер
 1       | Занят      |  **
 2       | Занят      |  **
 3       | Свободен | 40 м
 4       | Занят      | **
 5       | Свободен | 300 м
____________________________

Запуск 2 потоков .
* Поток №1 просит 30 метров , система проецирует 30 м в 3-ем регионе
* Поток №2 просит 10 байт , система выделяет 10 байт в 3-ем регионе  
   становится ясно что поток №2 наткнется на проблемы при  
   заимствовании  более 10 м.

Причем при первой итерации система недопустила не одной ошибки , она  выбрала регион который вполне удолетворял потребностям.

А теперь похожий код запускает Паша . У Паши присутствует только проблемный контекст этого кода, а именно пост 1 этой ветки поэтому у него
ситуация  другая  :

____________________________
Регион    Статус      Размер
 1       | Занят      |  **
 2       | Занят      |  **
 3       | Свободен | 400 м
 4       | Занят      | **
 5       | Свободен | 100 м
____________________________

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

Ты можешь все это проверить запускаешь 2 потока в которых убираешь
цикл (Repeat,Until) но память не освобождаешь в течеении к примеру 2-5 секунд тоесть :


 for i:=1 to l do GetMem(a[i],10);
Sleep(5000)
 for i:=1 to l do FreeMem(a[i]);  


Я думаю мысль понятна ?


 
SPACE!!   (2006-09-19 00:25) [21]

Самое правильное будет решение это сделать несколько буферов, зарезервировать нужное количество виртуальной памяти для них и
потом уже выделять и освобождать физическую память .
Как это все организовать  найдешь в книге . В поисковеке ищи Richter.chm .
Да и незабудь функция VirtualAlloc  не защищена покрайне мере ее
тебе будет нужно включить в критическую секцию. Посмотри также в
книге объект ядра проецируемые в память файлы, тоже может помочь
решить проблему.


 
Павел И   (2006-09-19 06:44) [22]


> SPACE!!   (19.09.06 00:25) [21]
>
> Самое правильное будет решение это сделать несколько буферов,
>  зарезервировать нужное количество виртуальной памяти для
> них и
> потом уже выделять и освобождать физическую память .


Спасибо, вероятно я примерно так и поступлю.


 
orinoko   (2006-09-19 11:09) [23]

Господа! А что мешает подключить в проект FastMemoryManager (FastMM)? Он как раз в данном случае очень поможет и сведёт к минимуму фрагментацию памяти при при активных выделениях-освобождениях



Страницы: 1 вся ветка

Форум: "Основная";
Текущий архив: 2006.10.29;
Скачать: [xml.tar.bz2];

Наверх





Память: 0.53 MB
Время: 0.046 c
15-1160399500
oldman
2006-10-09 17:11
2006.10.29
Как скопировать файлы с длинными русскими именами???


2-1160666423
i-am-vladko
2006-10-12 19:20
2006.10.29
потоки...


2-1161009570
funky
2006-10-16 18:39
2006.10.29
считывание опред. строки


15-1160119266
Ломброзо
2006-10-06 11:21
2006.10.29
Intranet-Web и документы Office


2-1160747147
dera
2006-10-13 17:45
2006.10.29
Вопрос любителям SQL





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский