Как бороться с “thread creation error: Недостаточно памяти..

Модераторы: Duncon, Naeel Maqsudov, Игорь Акопян, Хыиуду

Ответить
msn777
Сообщения: 5
Зарегистрирован: 16 янв 2005, 20:54

16 янв 2005, 20:58

Мастера подскажите, как бороться с “thread creation error: Недостаточно памяти для обработки команды”, говорит, что, мол, мало памяти, хотя под стек выделено 256M {$M 16384,268435456}, на машине стоит 512М, в диспетчере задач приложение показывает, что для приложения выделено около 5M. Пишу на Delphi 7 под WinXP.
Может, кто сталкивался с такой бедой, из каких соображений принимается решение, что не хватает памяти. До того как в проге было мало элементов (меньше сотни кнопок, меток и т.д.) такое сообщение не выскакивало, сейчас их несколько сотен, такое сообщение выскакивает, после того, как вызываю play_sound для проигрывания wav файла, но ф-н sndPlaySound все время выдает FALSE, хотя перенес этот модуль из старого проекта там все работало, а после этого еще раз play_sound но для пробирования тона, процесс (Thread) созданный sndPlaySound так и остается, а звука нет. Причем если вызывать sndPlaySound с тем же именем файла, но указанным как константа – звук есть, если же переменная (PChar) – звука нет.

Содержание ComboBox’ов (ItemIndex = -1 если задал имя файла ):
нет
100 Гц, 1 гудок
100 Гц, 2 гудка
200 Гц, 1 гудок
200 Гц, 2 гудка
300 Гц, 1 гудок
300 Гц, 2 гудка
500 Гц, 1 гудок
500 Гц, 2 гудка
700 Гц, 1 гудок
700 Гц, 2 гудка
1000 Гц, 1 гудок
1000 Гц, 2 гудка
1500 Гц, 1 гудок
1500 Гц, 2 гудка
2000 Гц, 1 гудок
2000 Гц, 2 гудка

type
TPlayToneThread = class(TThread) // Поток проигрывания тона
private
Frequency: integer; // Частота тона, Гц
Duration: integer; // Длительность выдачи тона, мс
Count: integer; // Кол-во выдаваемых тонов
protected
procedure Execute; override; // Исполняемая часть
end;

//----------------------------------- Выдать на динамик тон ----------------------------------------
procedure Sound(Frequency, Duration: Integer);
asm
push edx
push eax
mov eax, Win32Platform
cmp eax, VER_PLATFORM_WIN32_NT
jne @@9X
call Windows.Beep
ret
@@9X:
pop eax
pop edx
push ebx
push edx
mov bx, ax
mov ax, 34DDh
mov dx, 0012h
cmp dx, bx
jnc @@2
div bx
mov bx, ax
in al, 61h
test al, 3
jnz @@1
or al, 3
out 61h, al
mov al, 0B6h
out 43h, al
@@1:
mov al, bl
out 42h, al
mov al, bh
out 42h, al
call Windows.Sleep
in al, 61h
and al, 0FCh
out 61h, al
jmp @@3
@@2:
pop edx
@@3:
pop ebx
end;


//--------------------------------- Реализация потока проигрывания тона ----------------------------
procedure TPlayToneThread.Execute;
begin
FreeOnTerminate:=True; // По завершению работы освободить память
while Count>0 do
begin
Sound(Frequency,Duration);
sleep(Duration);
dec(Count);
end;
Terminate; // На всяк случай завершаем поток
end;


//--------------------------------- Процедура завершения потока ------------------------------------
procedure TfmOSC_Buzzer.end_thread(Sender: TObject);
begin
PlayToneThread:=nil; // На всяк случай уничтожаем объект
end;

//-------------------------------------- Проиграть звук --------------------------------------------
procedure TfmOSC_Buzzer.play_sound(cbSound: TComboBox; play: boolean = false);
const
Duration: integer = 75;
var
Frequency, Count: integer;
begin
if cbSound.ItemIndex=0 then exit; // Если нечего проигрывать то выходим

if cbSound.ItemIndex<0 then // Если это *.wav файл
try
sndPlaySound(PChar(cbSound.Text),SND_ASYNC); // то просто проигрываем его
except
end
else begin
Frequency:=StrToInt(Trim(Copy(cbSound.Text,1,4))); // Определили частоту и кол-во гудков
if Odd(cbSound.ItemIndex) then Count:=1 else Count:=2;
if IsWindowsNT then
begin
{
if PlayToneThread<>nil then // Если поток существует
if not PlayToneThread.Terminated then // Да он еще и не завершен
begin
if play then // Если нужно проиграть
PlayToneThread.Terminate // то завершили поток
else // Если событие
exit; // то выходим
end;
PlayToneThread:=TPlayToneThread.Create(true); // Создаем поток
PlayToneThread.OnTerminate:=end_thread; // Задали процедуру завершения
PlayToneThread.Priority:=tpNormal; // Задаем нормальный приоритет потоку
PlayToneThread.Frequency:=Frequency;
PlayToneThread.Duration:=Duration;
PlayToneThread.Count:=Count;
PlayToneThread.Resume; // Запускаем поток
}
{}
while Count>0 do
begin
Sound(Frequency,Duration);
sleep(Duration);
dec(Count);
end;
{}
end
else begin
while Count>0 do
begin
Sound(Frequency,Duration);
sleep(Duration);
dec(Count);
end;
end;
end;
end;
Eugie
Сообщения: 707
Зарегистрирован: 17 фев 2004, 23:59
Откуда: SPb

17 янв 2005, 09:27

Можно вопрос: зачем такой большой стек? Вот и причина ошибки - ведь при создании каждого доп. thread'а ОС резервирует в АП процесса по 256М. Даже если памяти много, такими темпами Вы всю ее быстро исчерпываете (для любого процесса размер его АП - 4Г, из них доступно не более половины). Тем более Вам не нужен такой громадный стек в thread'е, где нет ни рекурсивных процедур, ни большого числа локальных переменных.
msn777
Сообщения: 5
Зарегистрирован: 16 янв 2005, 20:54

17 янв 2005, 21:09

Я реализую виртуальный прибор (осцил, самописец и т.д.) там бывает, что кол-во отсчетов измерения несколько миллионов (например для самописца – запись сигнала в течении суток), если есть маленький выдает ошибку о том, что мало памяти (прога - http://www.usb-osc.narod.ru).
Донат
Сообщения: 6
Зарегистрирован: 17 янв 2005, 22:33

17 янв 2005, 22:55

кстати у меня собственно проблема связана примерно с тем-же=)
Аватара пользователя
Naeel Maqsudov
Сообщения: 2551
Зарегистрирован: 20 фев 2004, 19:17
Откуда: Moscow, Russia
Контактная информация:

17 янв 2005, 23:13

msn777, отсчеты измерений хранятся в стеке что ли?
Главное назначение стека - это обеспечить возвраты после вызовов процедур.
Второе назначение - хранение локальных переменных процедур.

Чем больше стек, тем глубже процедуры могут вызывать друг друга.

Для хранения же значений используют статические переменные или выделяют память в куче, используя менеджер памяти построенный на GetMem/FreeMem или используя Global-, Local-, VirtualAlloc (WinAPI).
msn777
Сообщения: 5
Зарегистрирован: 16 янв 2005, 20:54

18 янв 2005, 01:58

Да действительно полностью согласен, с тем, что источником ошибок был именно большой стек, после того как уменьшил его до 64M, при меньших размерах TeeChart начинает, ругается, что мало ему память при записи несколько миллионов отсчетов. Теперь не совсем ясно как сделать так, чтобы TeeChart не выдавал ошибок при значительном времени записи???

Eugie, а откуда Вы узнали, что каждого доп. thread'а ОС резервирует в АП процесса по 256М, где можно почитать об этом???
Аватара пользователя
Naeel Maqsudov
Сообщения: 2551
Зарегистрирован: 20 фев 2004, 19:17
Откуда: Moscow, Russia
Контактная информация:

18 янв 2005, 02:40

Воооот! Вооот оказывается в чем все дело!
TeeChart кушает память из кучи для создания точек.
Каждая точка это не просто несколько байт данных. Каждая точка в сериях TeeChart это отдельный объект (со своими полями и VMT и прочим).
Несколько десятков.... ну... тысяч точек - это нормально, но миллионов - это порочная практика.
Тут надо писать данные не в оперативную память, а в базу данных. Ну и переходить на TDBChart.
Это позволит уже не несколько, а сотни миллионов хначения хранить и отображать.

Вы меня не убедите, что Вам обязательно нужны несколько миллионов отсчетов на экране одновременно: В 1024х768 всего 786432 пикселей :)
Суть решения в том, что из базы выбирается ровно столько данных, сколько нужно для отображения на экране.

Подцепите какую-нибудь очень легкую поддержку DBF. Что-нить вроде TDbf поищите на Torry.ru

PS
Если Вы возьмете.... ну скажем TListBox и попытаетесь добавить туда миллион строк, то ваше приложение тоже упадет....
Делайте выводы....
Eugie
Сообщения: 707
Зарегистрирован: 17 фев 2004, 23:59
Откуда: SPb

18 янв 2005, 17:59

msn777, по основному вопросу - воспользуйтесь советами Naeel Maqsudov'а.
откуда Вы узнали, что каждого доп. thread'а ОС резервирует в АП процесса по 256М, где можно почитать об этом
Для каждого вновь создаваемого потока (thread) ОС резервирует память под стек. Ее размер либо явно задается при вызове API ф-ции CreateThread, либо (если этот параметр = 0) используется соотв.значение из заголовка программы (а туда записывается в момент компиляции на основе настроек проекта). Класс TThread реализует как раз вторую возможность, т.е. при создании каждого нового объекта типа TThread размер резервируемой под его стек памяти берется из exe-файла (в Вашем случае 256М). К сожалению, разработчики Delphi в своем TThread не позволяют явно задавать размер стека, а зря... :(

Еще пара замечаний по использованию TThread. Вы пытаетесь воспользоваться методом Terminate для остановки потока, но так, как написано, работать не будет. Terminate на самом деле не завершает поток, а просто устанавливает cвойство Terminated в True - предполагается, что в Execute выполняется некий цикл, в котором проверяется этот флаг, и если надо, производится выход из Execute. Поэтому же вызов Terminate в конце Execute абсолютно не имеет смысла.

Где прочитать - см. MSDN - Platform SDK: DLLs, Processes, and Threads и исходники Delphi
msn777
Сообщения: 5
Зарегистрирован: 16 янв 2005, 20:54

19 янв 2005, 05:04

Ок! Большое спасибо за понятные разъяснения.
Ответить