Размер стека для потока
Модератор: Модераторы
- Sergei I. Gorelkin
- энтузиаст
- Сообщения: 1409
- Зарегистрирован: 24.07.2005 14:40:41
- Откуда: Зеленоград
Между процессорами Intel и AMD нет разницы в расходе стека, по крайней мере если запускать готовую программу (если компилировать на каждой из машин, то может появиться разница из-за настроек оптимизации). Теоретически же для Win32 минимум 4 байта, для Win64 - 16 байт, если вызываемая процедура сама ничего не вызывает и 48 байт - если вызывает. А для процессоров SPARC минимум будет 96 байт, если я не ошибаюсь.
hinst писал(а):@VKB: я оказывается немного не то проверял.
Если создавать поток функцией Windows.CreateThread и передать размер стека 0, то он выделит 16 мегабайт. То есть, можно считать, что 16 мегабайт это размер стека для потока, который выделяет операционная система по умолчанию
У API-шной функции CreateThread есть своё правило "по умолчанию", FPC на него не влияет. http://msdn.microsoft.com/en-us/library ... 85%29.aspx:
Код: Выделить всё
dwStackSize [in]
The initial size of the stack, in bytes. The system rounds this value to the nearest page. If this parameter is zero, the new thread uses the default size for the executable. For more information, see Thread Stack Size.Это же правило работает и для fpc-шного BeginThread, если ему явно задать размер стека 0. Тогда стек выделится с таким же размером, какой был задан для основного потока. А он может быть и 16Мб, например. Я же пишу немного о другом. Функция BeginThread в FPC перегружена, и все её версии, которые не содержат параметра StackSize (в том числе и та, которая вызывается из конструктора TThread), в итоге вызывают "основную" версию и передают ей в качестве StackSize не 0, а константу DefaultStackSize, которая равна 4*1024*1024 (эта константа в принципе может отличаться в разных версиях FPC - я не проверял).
Что же касается остального, то Sergei I. Gorelkin уже объяснил. Затраты стека могут зависеть от разрядности приложения, наличия вложенных вызовов и настроек оптимизации.
wavebvg писал(а):...
2. Один факт того, что при завершении потока стоит Sleep в 100мс дает понять всю серьёзность проблемы
Что-то я не найду, где там Sleep(100)
Вот сейчас специально смотрел и не нашёл
hinst писал(а):Что-то я не найду, где там Sleep(100)
Вот сейчас специально смотрел и не нашёл
В реализаций "unix"
Для примера, попробуйте создать 100 потоков, запустить их и потом разом их завершить. Просто меня это очень сильно поразило в исходниках (на линуксе прекрасно работает, даже если убрать эту задержку)
Просто делал кое что с потоками в целях собственного просвящения link.
Когда приложение завершается и нужно остановить большой пул - немыслимые задержки. Думал сам накосячил. Через неделю начал копать нашел в исходниках fpc кусок с
Код: Выделить всё
sleep(100)А ну не знаю, я реализацию для Windows смотрел, там видимо нету
Никакого Sleep(100) в linux-версии тоже не нашел. fpc 2.6.2
В каком исходнике?
Добавлено спустя 22 минуты 52 секунды:
И да, 100 потоков прекрасно запускается и завершается. Например, следующий код работает ожидаемо быстро:
В каком исходнике?
Добавлено спустя 22 минуты 52 секунды:
И да, 100 потоков прекрасно запускается и завершается. Например, следующий код работает ожидаемо быстро:
Код: Выделить всё
{$mode objfpc}
uses cthreads,sysutils,dateutils;
const MaxThreads=300;
function increment(parameter: pointer):PtrInt;
begin result:=0; end;
var ths:array [1..MaxThreads] of TThreadID;
dt:tdatetime; i:ptruint;
begin
dt:=now();
for i:=1 to MaxThreads do ths[i]:=BeginThread(@increment,pointer(i));
for i:=1 to MaxThreads do WaitForThreadTerminate(ths[i],0);
writeln('Общее время работы: ',millisecondsbetween(now(),dt));
end.Смутил вот этот участок кода:
fpc/rtl/unix/tthread.inc
В Вашем примере (да и моих попытках воспроизведения), пока что не получается воспроизвести по аналогии. А вот если необходима синхронизация с основным потоком, тогда и возникают проблемы:
Зачем ждать эти злосчастные 100 мсек? Хотя в моих потугах в прошлом посте ошибка с задержкой не исчезает - нужно менять подход...
fpc/rtl/unix/tthread.inc
Код: Выделить всё
function TThread.WaitFor: Integer;
begin
WRITE_DEBUG('waiting for thread ',ptruint(FHandle));
If (MainThreadID=GetCurrentThreadID) then
{
FFinished is set after DoTerminate, which does a synchronize of OnTerminate,
so make sure synchronize works (or indeed any other synchronize that may be
in progress)
}
While not FFinished do
CheckSynchronize(100);
WaitFor := WaitForThreadTerminate(FHandle, 0);
{ should actually check for errors in WaitForThreadTerminate, but no }
{ error api is defined for that function }
FThreadReaped:=true;
WRITE_DEBUG('thread terminated');
end;В Вашем примере (да и моих попытках воспроизведения), пока что не получается воспроизвести по аналогии. А вот если необходима синхронизация с основным потоком, тогда и возникают проблемы:
Код: Выделить всё
{$mode objfpc}
uses cthreads,sysutils,dateutils,classes;
const MaxThreads=300;
type
{ TMyThread }
TMyThread = class(TThread)
private
FTime: Integer;
protected
procedure Execute; override;
procedure SyncOnTerminated(Sender: TObject);
public
constructor Create(CreateSuspended: Boolean; const StackSize: SizeUInt = DefaultStackSize);
property Time: Integer read FTime write FTime;
end;
function increment(parameter: pointer):PtrInt;
begin Sleep(Integer(parameter)); end;
var ths:array [1..MaxThreads] of TThreadID;
dt:tdatetime; i:ptruint;
var ths1:array [1..MaxThreads] of TMyThread;
{ TMyThread }
procedure TMyThread.Execute;
begin
Sleep(Time);
end;
procedure TMyThread.SyncOnTerminated(Sender: TObject);
begin
end;
constructor TMyThread.Create(CreateSuspended: Boolean; const StackSize: SizeUInt);
begin
inherited;
end;
begin
dt:=now();
for i:=1 to MaxThreads do ths[i]:=BeginThread(@increment,pointer(i*10));
for i:=1 to MaxThreads do WaitForThreadTerminate(ths[i],0);
writeln('Общее время работы: ',millisecondsbetween(now(),dt));
for i:=1 to MaxThreads do
begin
ths1[i]:=TMyThread.Create(true);
ths1[i].Time:=i*10;
ths1[i].OnTerminate := @ths1[i].SyncOnTerminated;
end;
dt:=now();
for i:=1 to MaxThreads do ths1[i].Resume;
for i:=1 to MaxThreads do ths1[i].WaitFor;
writeln('Общее время работы: ',millisecondsbetween(now(),dt));
for i:=1 to MaxThreads do ths1[i].Free;
end.Общее время работы: 3012
Общее время работы: 3101
Зачем ждать эти злосчастные 100 мсек? Хотя в моих потугах в прошлом посте ошибка с задержкой не исчезает - нужно менять подход...
Что-то до меня не дошло, где Вам приходится ждать 100мс. Если Вы про CheckSynchronize(100), то там - блокировка на событии (SynchronizeTimeoutEvent), с таймаутом. Т.е. если событие возникает, таймаут не пригождается. Задержки могут быть, если мне память не изменяет, только в случае если сейчас выполняется метод Synchronize для эксклюзивной работы с GUI.
Что касается Вашего второго примера, то Вы там сами делаете Sleep, в исполняемых методах. Естественно, что нити не завершат работу, пока не закончат каждая свой Sleep. Уберите его, и получите время работы от 10 до 200 мс на каждый набор из 300 потоков.
Что касается Вашего второго примера, то Вы там сами делаете Sleep, в исполняемых методах. Естественно, что нити не завершат работу, пока не закончат каждая свой Sleep. Уберите его, и получите время работы от 10 до 200 мс на каждый набор из 300 потоков.
xdsl писал(а):Что касается Вашего второго примера, то Вы там сами делаете Sleep, в исполняемых методах. Естественно, что нити не завершат работу, пока не закончат каждая свой Sleep. Уберите его, и получите время работы от 10 до 200 мс на каждый набор из 300 потоков.
Ну... Без задержки я получаю какое-то минимальное время, говорящее о времени создания/завершения потока + время на синхронизацию основного потока:
не [10-200], а [10-90]U[110-190], что намекает на то, что в этот странный цикл синхронизации можно легко и непринуждённо попасть. Прошу прощения на своё упорство, просто не вижу необходимости обрабатывать этот цикл:
Код: Выделить всё
While not FFinished do
CheckSynchronize(100);Ситуация, когда мы вызываем синхронизацию из основного потока и при этом из основного же потока вызывается WaitFor мне кажется очень неестественной.
1. Из основного потока вызываем JOIN
2. Второй поток может завершиться и вызвать синхронизацию с основным
3. Раз в 100мс основной просыпается, и если надо - выполняет код OnTerminate, если второй поток завершится
4. После чего вызывается DoTerminate, где меняется флаг
5. Цикл завершается
На мой взгляд это несколько странно, да и самый простой способ использования, когда пользователь в OnTerminate будет обрабатывать очередь, приводит к тому, что пул потоков в 300 ЗАВЕРШАЕМЫХ потоков простоит... 3 секунды (!!!) из-за синхронизации...
С другой стороны, понятно, что это делается для того, чтобы при количестве потоков > 2 (1 основной и 2 вторичных), можно было бы основной поток заткнуть в JOIN для первого вторичного, и при этом второй вторичный мог бы вызвать и обработать OnTerminate...
Давайте закроем вопрос до того момента, как у меня на руках будет самостоятельная реализация данного класса, проходящего все тесты на нескольких платформах, чтобы понять все подводные камни... Хотя я в это и не верю...
PS. Мне тоже кажется странным, откуда возникают эти 100мс, ведь вызов CheckSynchronize(100) не должен создавать задержек, которые явно возникают!!!
Кто хочет: есть реализация кроссплатформенного потока от MSEIDE-MSEGUI: https://gitorious.org/mseide-msegui/mse ... thread.pas
Добавлено спустя 7 минут 25 секунд:
в MSE библиотеки как-то немного более структурированы: есть отдельно какие-то модули: mseclasses, msestream, mselist, msethread, mseevent, а не всё в Classes с include-файлами
Добавлено спустя 7 минут 25 секунд:
в MSE библиотеки как-то немного более структурированы: есть отдельно какие-то модули: mseclasses, msestream, mselist, msethread, mseevent, а не всё в Classes с include-файлами
Delphi, кстати, передаёт 0 в BeginThread в качестве размера стека, что интерпретируется как "такой же размер стека, как и для основного потока".
Вот во время оно я стал изучать потоки и выяснилось, что Windows 98 гарантированно падает, если ей передавать 0. Причём, не просто программа, а винда целиком. Я тогда это как баг FPC докладывал, объяснял, что ружья кирпичом не чистят, и надо передавать хоть какое-то значение.
Прислушались, значит.
- Sergei I. Gorelkin
- энтузиаст
- Сообщения: 1409
- Зарегистрирован: 24.07.2005 14:40:41
- Откуда: Зеленоград
Поведение CreateThread с нулевым размером стека документировано в MSDN и не менялось со времен Windows 95. Если бы оно роняло всю винду, ни одна программа на Дельфи не работала бы. А они работали, и их было много... некоторые работают и до сих пор.
Так, случайно набрёл на замечание по теме для unix стека.
