скалогрыз
Я ошибся, сделал тот же код на Дельфе, поведение такое же неадекватное.
«где такое написано?»
Я искал описания работы критической секции в подробностях и нашёл такое:
Когда поток выполняет вход в критическую секцию, он проверяет хозяина критической секции, если хозяин не он сам, то создаётся объект синхронизации, и поток начинает ожидать сигнальное состояние только, что созданного объекта. Когда поток выходит из критической секции, то выполняется проверка наличия созданного (кем-то другим) объекта синхронизации, если такой объект есть, то поток выходящий из КС, перестаёт быть хозяином и выставляет сигнальное состояние у объекта синхронизации.
Это можно назвать очередью из 1 одного элемента.
«гарантирует тот факт, что EnterCriticalSection 2 произойдёт только после того как отработал соседний поток ожидающий cs?» — Если честно, не очень понял этот вопрос.
Вы писали:
«
* Твой поток-счётчик, запускается и забирает критическую секцию (ну и спит с ней).
* В основном потоке, приходит сообщение: «Нажата кнопка».
* Основной поток идёт к крит-секции и ждёт пока она освободится (т.е. пока спит поток-счётчик).
* После захвата крит-секции он засыпает на 5 секунд (что вызывает эффект «залипания» т.к. другие сообщения не обрабатываются).
* (к этому времени, поток-счётчик уже ждёт своей очереди на сон, ожидая крит-секцию)
* Поспав пять секунд, основной поток возвращается в норму.
«
я с этим согласен, именно такого поведения я и ожидал, проблема в » Основной поток идёт к крит-секции и ждёт пока она освободится (т.е. пока спит поток-счётчик). «, основной поток никак не может дождаться критической секции не смотря на то, что поток-счётчик уходит в Sleep, что означает, что у основного потока куча времени на то, чтобы захватить КС.
» Исключать нужно короткие (по времени исполнения) куски кода.» — что это значит? Как я уже писал, если внутри потока счётчика убрать Sleep (т.е. поток-счётчик только и будет входить в КС, увеличивать счётчик, выводить его, выходить из КС) , то приведённый в самом начале код, работает как ожидается.
«Пока работает Sleep — другие отдыхаю..» — поработает Sleep, другие работают. Sleep для каждого потока свой.
» у тебя один поток сильно зависит от другого..» — Тут как раз наоборот, поток-счётчик никак не хочет быть зависимым от основного потока, потому, что при попытке входа основным потоком в КС, поток счётчик работает так, как будто никто другой и не пытается войти в КС.
Добавлено спустя 10 минут 34 секунды:
Вот http://rsdn.org/article/baseserv/critsec.xml где написано. Там сказано, что поток который выходит из КС, сам проверяет наличие попытки другого потока войти в КС, если такая попытка была (пока он занимался своими делами, к примеру тупо спал), то поток хозяин КС выставляет объект синхронизации в сигнальное состояние, и поток который ожидал этот сигнальный объект пробуждается.
Добавлено спустя 5 минут 44 секунды:
- Код: Выделить всё
repeat
CritSec.Acquire;
Inc(Counter);
WriteLn(Format('Counter=%d',[Counter]));
Sleep(100);
CritSec.Release;
[b]SwitchToThread;[/b]
until Self.Terminated;
Использование SwitchToThread; тоже не помогает (попробовал в Винде), как обычно, основной поток висит на ожидании КС, поток-счётчик, считает, выводит и не парится.
Добавлено спустя 24 минуты 13 секунд:
Вот другой вариант на Дельфи.
- Код: Выделить всё
unit Unit1;interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, SyncObjs, ExtCtrls;type
TSimpleThread = class(TThread)
protected
procedure Execute; override;
public
Delta:Integer;
end;TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
Memo1: TMemo;
Button3: TButton;
Timer1: TTimer;
Button4: TButton;
procedure Button1Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure Timer1Timer(Sender: TObject);
procedure Button4Click(Sender: TObject);
private
FSTh1:TSimpleThread;
FSTh2:TSimpleThread;
end;var
Form1: TForm1;
CritSec:TCriticalSection;
Counter:Integer;implementation
{$R *.dfm}
{ TSimpleThread }
procedure TSimpleThread.Execute;
begin
repeat
CritSec.Acquire;
Counter:=Counter+Self.Delta;
Sleep(100);
CritSec.Release;
until Self.Terminated;
end;procedure TForm1.Button1Click(Sender: TObject);
begin
FSTh1:=TSimpleThread.Create(True);
FSTh1.FreeOnTerminate:=True;
FSTh1.Delta:=1;FSTh2:=TSimpleThread.Create(True);
FSTh2.FreeOnTerminate:=True;
FSTh2.Delta:=-1;FSTh1.Resume;
FSTh2.Resume;
end;procedure TForm1.Button3Click(Sender: TObject);
begin
FSTh1.Terminate;
end;procedure TForm1.Button2Click(Sender: TObject);
begin
CritSec.Acquire;
Memo1.Lines.Append(Format('Counter=%d',[Counter]));
Sleep(1000);
CritSec.Release;
end;procedure TForm1.FormCreate(Sender: TObject);
begin
CritSec:=TCriticalSection.Create;
end;procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
CritSec.Free;
end;procedure TForm1.Timer1Timer(Sender: TObject);
begin
Memo1.Lines.Append(Format('Counter=%d',[Counter]));
end;procedure TForm1.Button4Click(Sender: TObject);
begin
FSTh2.Terminate;
end;end.
Основной поток только выводит (по таймеру) значение счётчика в Memo. Один поток уменьшает счётчик, другой увеличивает. Казалось бы, если потоки используют одну критическую секцию, то значение счётчика не должно меняться, вместо этого счётчик постоянно меняется, значит один поток спокойно себе работает заходя в КС, меняя счётчик, и выходя из КС, а другой поток не может дождаться входа в КС, хотя другой поток постоянно захватывает и освобождает КС.
Обновлено: 28.01.2023
Юрий Балыкин
дата публикации 15-07-2008 03:39
Работа с потоками в Delphi: так ли страшен чёрт, как его малюют?
Данная статья предназначена для начинающих программистов, которые никогда не работали с потоками, и хотели бы узнать основы работы с ними. Желательно, чтоб читатель знал основы ООП и имел какой-нибудь опыт работы в Delphi. Для начала давайте определимся, что под словом «поток» я подразумеваю именно Thread, который еще имеет название «нить».
Нередко встречал на форумах мнения, что потоки не нужны вообще, любую программу можно написать так, что она будет замечательно работать и без них. Конечно, если не делать ничего серьёзней «Hello World» это так и есть, но если постепенно набирать опыт, рано или поздно любой начинающий программист упрётся в возможности «плоского» кода, возникнет необходимость распараллелить задачи. А некоторые задачи вообще нельзя реализовать без использования потоков, например работа с сокетами, COM-портом, длительное ожидание каких-либо событий, и т.д.
Всем известно, что Windows система многозадачная. Попросту говоря, это означает, что несколько программ могут работать одновременно под управлением ОС. Все мы открывали диспетчер задач и видели список процессов. Процесс — это экземпляр выполняемого приложения. На самом деле сам по себе он ничего не выполняет, он создаётся при запуске приложения, содержит в себе служебную информацию, через которую система с ним работает, так же ему выделяется необходимая память под код и данные. Для того, чтобы программа заработала, в нём создаётся поток. Любой процесс содержит в себе хотя бы один поток, и именно он отвечает за выполнение кода и получает на это процессорное время. Этим и достигается мнимая параллельность работы программ, или, как её еще называют, псевдопараллельность. Почему мнимая? Да потому, что реально процессор в каждый момент времени может выполнять только один участок кода. Windows раздаёт процессорное время всем потокам в системе по очереди, тем самым создаётся впечатление, что они работают одновременно. Реально работающие параллельно потоки могут быть только на машинах с двумя и более процессорами.
Для создания дополнительных потоков в Delphi существует базовый класс TThread, от него мы и будем наследоваться при реализации своих потоков. Для того, чтобы создать «скелет» нового класса, можно выбрать в меню File — New — Thread Object, Delphi создаст новый модуль с заготовкой этого класса. Я же для наглядности опишу его в модуле формы. Как видите, в этой заготовке добавлен один метод — Execute. Именно его нам и нужно переопределить, код внутри него и будет работать в отдельном потоке. И так, попробуем написать пример — запустим в потоке бесконечный цикл:
Запустите пример на выполнение и нажмите кнопку. Вроде ничего не происходит — форма не зависла, реагирует на перемещения. На самом деле это не так — откройте диспетчер задач и вы увидите, что процессор загружен по-полной. Сейчас в процессе вашего приложения работает два потока — один был создан изначально, при запуске приложения. Второй, который так грузит процессор — мы создали по нажатию кнопки. Итак, давайте разберём, что же означает код в Button1Click:
- tpTimeCritical — критический
- tpHighest — очень высокий
- tpHigher — высокий
- tpNormal — средний
- tpLower — низкий
- tpLowest — очень низкий
- tpIdle — поток работает во время простоя системы
Думаю, теперь вам понятно, как создаются потоки. Заметьте, ничего сложного. Но не всё так просто. Казалось бы — пишем любой код внутри метода Execute и всё, а нет, потоки имеют одно неприятное свойство — они ничего не знают друг о друге. И что такого? — спросите вы. А вот что: допустим, вы пытаетесь из другого потока изменить свойство какого-нибудь компонента на форме. Как известно, VCL однопоточна, весь код внутри приложения выполняется последовательно. Допустим, в процессе работы изменились какие-то данные внутри классов VCL, система отбирает время у основного потока, передаёт по кругу остальным потокам и возвращает обратно, при этом выполнение кода продолжается с того места, где приостановилось. Если мы из своего потока что-то меняем, к примеру, на форме, задействуется много механизмов внутри VCL (напомню, выполнение основного потока пока «приостановлено»), соответственно за это время успеют измениться какие-либо данные. И тут вдруг время снова отдаётся основному потоку, он спокойно продолжает своё выполнение, но данные уже изменены! К чему это может привести — предугадать нельзя. Вы можете проверить это тысячу раз, и ничего не произойдёт, а на тысяча первый программа рухнет. И это относится не только к взаимодействию дополнительных потоков с главным, но и к взаимодействию потоков между собой. Писать такие ненадёжные программы конечно нельзя.
Вот мы и подошли к очень важному вопросу — синхронизации потоков.
Если вы создали шаблон класса автоматически, то, наверное, заметили комментарий, который дружелюбная Delphi поместила в новый модуль. Он гласит: «Methods and properties of objects in visual components can only be used in a method called using Synchronize». Это значит, что обращение к визуальным компонентам возможно только путём вызова процедуры Synchronize. Давайте рассмотрим пример, но теперь наш поток не будет разогревать процессор впустую, а будет делать что-нибудь полезное, к примеру, прокручивать ProgressBar на форме. В качестве параметра в процедуру Synchronize передаётся метод нашего потока, но сам он передаётся без параметров. Параметры можно передать, добавив поля нужного типа в описание нашего класса. У нас будет одно поле — тот самый прогресс:
Вот теперь ProgressBar двигается, и это вполне безопасно. А безопасно вот почему: процедура Synchronize на время приостанавливает выполнение нашего потока, и передаёт управление главному потоку, т.е. SetProgress выполняется в главном потоке. Это нужно запомнить, потому что некоторые допускают ошибки, выполняя внутри Synchronize длительную работу, при этом, что очевидно, форма зависает на длительное время. Поэтому используйте Synchronize для вывода информации — то самое двигание прогресса, обновления заголовков компонентов и т.д.
Вы наверное заметили, что внутри цикла мы используем процедуру Sleep. В однопоточном приложении Sleep используется редко, а вот в потоках его использовать очень удобно. Пример — бесконечный цикл, пока не выполнится какое-нибудь условие. Если не вставить туда Sleep мы будем просто нагружать систему бесполезной работой.
Теперь мы немного изменим, можно сказать даже упростим, реализацию метода Execute нашего потока:
Вот, в принципе, мы и рассмотрели основные способы работы с компонентами VCL из потоков. А как быть, если в нашей программе не один новый поток, а несколько? И нужно организовать работу с одними и теми же данными? Тут нам на помощь приходят другие способы синхронизации. Один из них мы и рассмотрим. Для его реализации нужно добавить в проект модуль SyncObjs.
Самый интересный способ, на мой взгляд — критические секции
Работают они следующим образом: внутри критической секции может работать только один поток, другие ждут его завершения. Чтобы лучше понять, везде приводят сравнение с узкой трубой: представьте, с одной стороны «толпятся» потоки, но в трубу может «пролезть» только один, а когда он «пролезет» — начнёт движение второй, и так по порядку. Еще проще понять это на примере и тем же ProgressBar’ом. Итак, запустите один из примеров, приведённых ранее. Нажмите на кнопку, подождите несколько секунд, а затем нажмите еще раз. Что происходит? ProgressBar начал прыгать. Прыгает потому, что у нас работает не один поток, а два, и каждый из них передаёт разные значения прогресса. Теперь немного переделаем код, в событии onCreate формы создадим критическую секцию:
У TCriticalSection есть два нужных нам метода, Enter и Leave, соответственно вход и выход из неё. Поместим наш код в критическую секцию:
Попробуйте запустить приложение и нажать несколько раз на кнопку, а потом посчитайте, сколько раз пройдёт прогресс. Понятно, в чем суть? Первый раз, нажимая на кнопку, мы создаём поток, он занимает критическую секцию и начинает работу. Нажимаем второй — создаётся второй поток, но критическая секция занята, и он ждёт, пока её не освободит первый. Третий, четвёртый — все пройдут только по-очереди.
Критические секции удобно использовать при обработке одних и тех же данных (списков, массивов) разными потоками. Поняв, как они работают, вы всегда найдёте им применение.
В этой небольшой статье рассмотрены не все способы синхронизации, есть еще события (TEvent), а так же объекты системы, такие как мьютексы (Mutex), семафоры (Semaphore), но они больше подходят для взаимодействия между приложениями. Остальное, что касается использования класса TThread, вы можете узнать самостоятельно, в help’е всё довольно подробно описано. Цель этой статьи — показать начинающим, что не всё так сложно и страшно, главное разобраться, что есть что. И побольше практики — самое главное опыт!
Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter.
Функция может не работать в некоторых версиях броузеров.
. when altering one’s mind becomes as easy as programming a computer, what does it mean to be human.
8 июня 2010 г.
Как узнать, почему зависла программа?
Сегодняшняя статья будет посвящена нескольким подходам к отладке зависаний программы.
Состоит она из шести частей:
— Подготовка — общие действия для всех случаев отладки.
— Delphi — отладка зависания в Delphi.
— EurekaLog — поиск причины зависания в EurekaLog.
— Process Explorer — поиск причины зависания утилитой Process Explorer.
— Threads Snapshot — поиск причины зависания утилитой Threads Snapshot.
— Практический пример — пример с искусственным зависанием в программе.
Ну, прежде чем приступать к отладке программы, вам надо бы её пересобрать (делайте Build, а не просто Compile), добавив в неё отладочную информацию. Для разных подходов требуется разные форматы отладочной информации, поэтому лучше включить всё сразу, чтобы не думать 🙂 Это необходимо сделать, чтобы отладочные средства могли показывать вам читабельные названия процедур и номера строк в исходниках. В противном случае, вам придётся иметь дело с машинным кодом и смещениями.
Итак, вам нужно включить (Project/Options/Linking): «MAP file» — Detailed, «Debug Information» (в старых версиях Delphi она называлась «Include TD32 debug info») — True, «Include remote debug symbols» — True:

Кроме отладочной информации, полезно включить опции «Stack Frames» и «Use Debug DCUs» на вкладке «Compiling»:

Уж позвольте мне не повторяться, что делают эти опции.
Не забываем сделать Build после изменения опций. Понятно, что если у вас несколько проектов (DLL, BPL и т.п.), то менять опции и пересобирать надо все. Также, если вы используете сборку с run-time пакетами — по возможности, выключите их на время тестирования, ибо пакеты сильно всё усложняют.
Вы можете подумать: «но я не могу воспроизвести это под отладчиком!» или «на проблемной машине у меня не стоит Delphi!». Но не спешите переходить к следующему разделу.
Во-первых, вам необязательно запускать программу в Delphi. Вы можете запустить программу как обычно, вне среды, и работать с ней, пока она не зависнет — после чего подключить к ней отладчик. Во-вторых, если на машине не стоит Delphi — вы можете установить на неё удалённый отладчик. Подробнее об удалённом отладчике — см. справку или мою статью (большой объём, вот вариант в PDF) — раздел 2, в конце секции 2.1.1. про работу с отладчиком.
Для отладки зависшего проекта в Delphi его, конечно же, нужно открыть. Предварительно он должен быть скомпилирован — с установленными опциями отладки, как мы сделали выше. Кроме того, для локальной машины желательно, чтобы вы запускали программу из Output path, указанном в опциях проекта (т.е. не перемещали бы скомпилированный exe перед запуском).
Итак, вы запустили свою программу, она сколько-то там поработала и зависла. Открываем Delphi, загружаем нужный проект и делаем Run/Attach to process:

Из списка выбираем вашу программу (введя при необходимости имя удалённой машины, если Delphi и программа находятся на разных машинах), устанавливаем галочку «Pause after attach» и жмём «Attach».
Отладчик подключится к процессу и установит его на паузу — путём возбуждения точки останова в потоке отладчика:

Вы можете игнорировать поток отладчика — просто перейдите на окно Threads и выберите любой свой поток для его отладки. Вы можете просмотреть стек вызовов, переключаться между потоками, анализировать переменные, делать пошаговое выполнение и т.п. — в общем, пользоваться отладчиком Delphi как обычно. Если вы включали отладочную информацию, то вы можете закрыть окно CPU и пользоваться отладкой по исходному тексту.
Напомню, что при возможности отладку зависаний стоит производить в ОС Vista и выше — потому что в этих системах появилась новая возможность для отладчиком: Wait Chain Traversal. Отладчик Delphi последних версий поддерживает WCT. Поэтому, если вы используете BDS 2009 или выше и Windows Vista или выше, то в окне Threads напротив каждого потока в колонке «Wait Chain» можно увидеть его статус, чего он ждёт, есть ли взаимоблокировка и т.п.:

В EurekaLog есть фишка «Anti-Freeze». Собственно, её я уже разбирал в отдельной статье, поэтому не буду останавливаться сейчас — у нас и так сегодня куча материала.
Если же по каким-то причинам среда Delphi для вас недоступна — вам придётся производить отладку руками.
Перед тем, как производить отладку, надо подготовить Process Explorer. Здесь будет два шага — оба опциональных, но для максимального удобства лучше сделать оба.
Шаг первый — настроить Process Explorer на загрузку отладочных символов. Дело в том, что Windows поставляется с обычными исполняемыми модулями без отладочной информации. Для своих программ мы только что включили генерацию отладочной информации (см. первый пункт), то как мы можем сделать это для Windows, чьи исходники нам недоступны? Ну, Microsoft позаботилась об этом: она распространяет отладочную информацию для своих программ отдельно. Вы можете скачать её и получить читабельные стеки вызовов.
Для начала вам понадобится скачать и установить Windows Debugging Tools. Затем, вам нужно решить, качать ли всю отладочную информацию скопом или же пусть она качается по запросу отладочной программы. Если вы выбрали первый путь — то вперёд, качаем и устанавливаем. Лично я выбираю второй путь.
Для второго способа вам нужно создать папку на своей машине с правом чтения-записи файлов и папок в ней. После чего остаётся только настроить Process Explorer:

В первом поле указывается путь к DbgHelp.dll — если вы устанавливали Windows Debugging Tools, то берите библиотеку оттуда. Если нет — то берите C:WindowsSystem32dbghelp.dll (однако я не уверен, будет ли это работать).
После того, как вы это настроили, Process Explorer будет пытаться получить отладочную информацию о каждом необходимом файле и кэшировать её в указанной папке. Поэтому, когда вы просматриваете потоки процесса или их стек вызовов, вы можете иногда видеть надпись «Loading symbols for ABC.exe+0xXYZ. «. В общем, после этого вам станет доступно больше информации для системных модулей.
Второй момент, который нужно сделать — сконвертировать map-файл вашего проекта в формат, понимаемый Process Explorer. Дело в том, что Delphi создаёт только различные Borland-ские форматы отладочной информации, а Process Explorer, как утилита Microsoft, понимает только Microsoft-ские форматы отладочной информации. Я уже говорил об этом. Сделать это можно утилитой map2dbg. Это простая консольная утилитка. Качаете архив, распаковываете, открываете консоль и пишете:
По файлам Project1.exe и Project1.map утилита сделает вам файл Project1.dbg, который может быть использован в Microsoft-ских утилитах.
Что-ж, я тут много чего сказал. Давайте я продемонстрирую, что вы получаете, выполнив указанные выше вещи. Ниже — три скриншота. Слева направо: вид стека вызовов без выполнения обоих пунктов (т.е. без системной и без проектной отладочной информацией), вид стека вызовов с первым пунктом (с системной, но без проектной отладочной информацией) и вид стека вызовов с обоими пунктами (с системной и с проектной отладочной информацией):



Как видите, подключение отладочной информации даёт вам три вещи:
— Более читабельный стек вызовов (вместо имя-модуля+смещение вы получаете имя-модуля!процедура+смещение)
— Более полный стек вызовов (без отладочной информации эвристика трассировки стека может опускать вызовы)
— Более правдивый стек вызовов (анализатор может неверно определять имя функции, если идёт вызов внутренней функции, которая не имеет публичного имени, но рядом находится другая функция, которая как-раз таки имеет публичное имя — поэтому анализатор может посчитать внутреннюю функцию частью публичной)
Если вы не будете (или не сможете) подключать отладочную информацию — вам придётся работать со смещениями. Вы конечно, можете искать смещения в map-файле руками, но это весьма хлопотно. Гораздо проще просто выписать на бумажку все числа, приписав имя модуля. Затем запускаете проект у себя, ставите его на паузу и используете команду Search/Goto address. Вводите адрес — и Delphi переводит вас на строчку в исходном тексте, а если это невозможно — то открывает окно CPU. Какой вводить адрес? Два примера. Вы выписали Project1.exe + $1234 и Project2.dll + $4321. Базовый адрес exe обычно не меняется и равен $400000. Вы загрузили проект у себя. Базовый адрес exe тот-же — $400000, а вот DLL оказалась загруженной по адресу $50000000 (вы можете выяснить это в окне Modules: View/Debug Windows/Modules). Тогда вас интересуют адреса $400000 + $1234 = $401234 и $50000000 + $4321 = $50004321.
Фух, разобрались. Как видите, намного удобнее подключать отладочную информацию, чем работать без неё 🙂
Теперь, что вы собственно должны делать для отладки зависания. Ну, вы запускаете свою программу и работаете с ней, пока она не зависнет. Потом вы запускаете Process Explorer, выбираете в списке процессов свою программу, правый щелчок -> Properties (свойства). В свойствах процесса переходим на вкладку Threads (потоки):

Здесь вы можете посмотреть, чем занимаются потоки вашей программы. Кто кушает процессор, кто чего-то ждёт и т.п. Выберите поток и нажмите кнопку «Stack» для просмотра его стека вызова (пример окна — см. три скриншота чуть выше).
Конечно, вы не сможете посмотреть переменные или что-то такое — только состояние и точки выполнения потоков. Так что вам придётся использовать свои телепатические способности, чтобы определить причину зависания.
Ну, использование Process Explorer хотя и несложно, но не выглядит таковым. Для новичка тут много работы и новых понятий. Да и вся эта возня с отладочной информацией не слишком удобна. Поэтому я написал простую утилитку (сейчас — часть EurekaLog Tools), которая позволяет вам выбрать запущенный процесс и дампит в текстовый файл информацию по всем потокам, включая:
— Базовая инфа: ID, приоритет и т.п.
— Стек вызова. Используются следующие источники отладочной информации: Borland-ские, JCL, EurekaLog и madExcept. Чуть позже добавлю поддержку Microsoft-ских и закачку по запросу, как у Process Explorer.
— Информация от Wait Chain Traversal (на Windows Vista и выше).
— Контекст потока — регистры и флаги.
Собственно, вы запускаете свою проблемную программу и работаете в ней, пока она не зависнет. Потом запускаете утилиту Threads snapshot и тыркаете её на зависший процесс. Она снимет вам снимок потоков, который вы сможете проанализировать.

Как видите — достаточно простая и удобная альтернатива ручной отладке с Process Explorer. Дополнительный плюс — вы можете попросить вашего клиента снять вам снимок процесса на его машине. В случае же с Process Explorer-ом — навряд ли вы сможете объяснить клиенту, что куда ставить и где жать. Не забудьте только передать все необходимые файлы (map-файлы, например).
Я написал эту утилиту буквально только что за два дня. Поэтому она «немножко» сыровата. Нормальная и отлаженная версия этой утилиты должна войти в EurekaLog v7.
Я написал простую программу с двумя кнопками. Вы можете использовать её как обучающий пример. Первая кнопка создаёт несколько потоков, которые ждут друг друга. Вторая кнопка вызывает порчу памяти, приводящую к зависанию. Запустите программу, нажмите на кнопку и попробуйте выяснить причину зависания. Посмотрим, как мы сможем отладить эти два случая.
В Delphi: если у вас есть поддержка WCT, то отладка первой кнопки вообще не представляет сложностей: запустили, нажали, поставили программу на паузу и смотрим окно Threads:

Если же WCT у вас нет, то придётся поработать головой и руками. Вы должны проанализировать стеки вызова каждого потока: щёлкаете по потокам в окне «Threads» и смотрите стеки вызовов:

После переключения на поток открывается окно CPU с текущей выполняемой инструкцией, но вы можете щёлкать по строчкам в окне «Call stack», чтобы посмотреть исходный код.
Проанализировав стеки вызовов всех трёх потоков, вы составите картину произошедшего.
Как эта же ситуация выглядит в Process Explorer и Threads snapshot? Ну, вы запускаете Process Explorer и смотрите стеки потоков (у меня первый поток открывался около минуты, не знаю, с чем связано — потом пошло гладко):

Хотя вы не видите здесь номеров строк, но вы видите имена функций и последовательность вызовов — например, в примере выше обратите внимание на Thread1Func, различные dispatch-функции, CriticalSection.Aquire и т.п. И снова: вы смотрите стеки вызова всех потоков и реконструируете ситуацию. Сделать это будет сложнее, чем используя Delphi (ибо нет номеров строк), но с известной долей телепатии — вполне возможно.
Что касается Threads snapshot, то она даёт такой лог (я отрезал лишние части для уменьшения лога):
К сожалению, вывод WCT пока не очень форматирован (это моё домашнее задание! 🙂 ), но цепочку ожиданий увидеть можно. Плюс стеки потоков (к сожалению, без системной отладочной информации — это моё второе домашнее задание!) самым решительным образом намекают на происходящее.
Второй случай сложнее. Потому что зависание -лишь внешнее проявление другой проблемы.
Итак, в Delphi вы запускаете программу, она виснет, мы ставим её на паузу. У нас только один поток, поэтому WCT нам не помощник, даже если он есть. Поэтому сразу переключаемся на главный поток и смотрим стек вызовов:

Опять-таки: щёлкая по стеку вызовов, мы видим исходный код.
В этот раз, простой анализ стека вызовов нам не помогает. Пока что неясно, что же случилось. Тем более, что программа вроде бы не висит, а что-то делает (т.е., строго говоря, у нас не зависание, а зацикливание). Поэтому мы начинаем пошаговый прогон программы. Пройдясь по коду мы видим, что код постоянно крутит цикл со Sleep, проверяя некий флаг. Мы видим, что этот код — код менеджера памяти FastMM. Почитав комментарии в коде, мы узнаём, что FastMM пытается получить блокировку. А проверяемый флаг — это признак занят/свободен. Поскольку FastMM проверяет этот флаг уже полчаса — ясно, что тут что-то не так. Этот же вывод следует из того, что в нашёй программе всего один поток — т.е. ждать-то и вовсе некого, кроме нас в программе никого нет. Иными словами, состояние флага не соответствует действительности — т.е. он испорчен вследствие повреждения памяти.
К сожалению, найти повреждение памяти не так-то просто и это отдельная тема, которую я недавно закончил обсуждать.
Поэтому, задача анализа зависания — выяснить причину. Мы её нашли: это — повреждение памяти. Дальше, анализ зависания закончен, но проблема пока не найдена и не устранена — мы переходим к анализу и поиску проблем с памятью.
Собственно, второй случай выглядит примерно аналогично везде (в Delphi, Process Exporer и Threads snapshot): мы получаем примерно один и тот же стек вызовов, который может меняться время от времени, но всегда это будет цикл и с вероятностью в 99% — стоять на Sleep. Делается просто несколько проверок, чтобы в этом убедиться. Для примера — вот как это выглядит в Process Explorer:

Напомню, что из этого мы вынесем только вывод (не однозначный, конечно же, а всего лишь обоснованное предположение), что у нас есть повреждение памяти. Дальше — это уже другая история.
Фух. На сегодня у меня всё. Надеюсь, этот материал был полезен. Если хотите, вот ещё дополнение — презентация по ручному поиску места ошибки по адресу (см. «How to find the exception source line»). На английском, но может пригодится или будет интересно.

Закрытие дочернего окна вызывает закрытие программы
Здравствуйте! Не могу никак разобраться, как сделать так, чтобы дочернее окно при его закрытии не.

Закрытие файла вызывает падение программы
Подскажите, в чём дело. Создаю два двоичных файла со списками слов, перед каждым словом — пишу.
еременная которой нигде нет(не описана) не вызывает ошибку в большом инете, а на локале вызывает ошибку
Совсем я ничего не понимаю. Переменная которой нигде нет(не описана) не вызывает ошибку в большом.
DateTime имеет формат MM.MM.yyyy H:mm:ss, что и вызывает ошибку
Проблема в том, что при вводе даты в формате 13.05.2014 16:13:34 появляется ошибка: При вводе.
Пока до таймера дело не доходит.
— Не загружен шрифт FontID(1)
— StartDrawing/StopDrawing не определён, куда рисовать то?
Все ошибки пишет среда PUreBasic внизу экрана, просто читайте и исправляйте.
Простите, торопился. Конечно же после объявления Global переменных:
Все ошибки пишет среда PUreBasic внизу экрана, просто читайте и исправляйте.
В том то и дело, что код проходит проверку синтаксиса и после запуска никаких ошибок, а тупо вылет.
Значит я пока настолько тупой, что не понял принцип рисования в окне. (((
Буду разбираться. Подскажите, направите на путь правильный?
Спасибо!
Принцип один, перед рисованием указываем место, где будем рисовать, при помощи StartDrawing.
StartDrawing выполняется довольно медленно, потому желательно выносить по возможности за пределы длинных циклов,
внутри циклов только сами операторы рисования.
Кол-во StartDrawing должно быть равно кол-ву StopDrawing.
Нет. Спасибо! Буду изучать. Хотя у меня там много намешано.
Стыдно весь код представить )
Ожидание запуска исполняемого файла.
Тип исполняемого файла: Windows — x86 (32bit, Unicode)
Запущен исполняемый файл.
[ОШИБКА] Строка: 27
[ОШИБКА] StartDrawing() должна быть вызвана перед другой 2DDrawing функцией.
Всё получилось. Спасибо!
Только не придумал как сделать пропорции для часовой и минутной стрелки от разрешения экрана. При небольших размерах вроде нормально выглядят, а на высоких часовая слишком длинная.
Это самая первая моя программа на PureBasic.
Clock_acco.7z
Не знал. Изучал коды и везде видел объявление переменной. Пришёл к выводу, что все примеры очень древние и их приходится допиливать. Так и изучаю PB )
Так же есть встроенные функции перевод градусов в радианы и обратно.
Тут просто — Бейсик вычисления делает в радианах, а не в градусах. Даже на советских калькуляторах был такой переключатель «рад-гр», там тоже в формулах обычно радианы использовались.
Непонятно ведёт себя при выборе данного скринсейвера и при нажатии кнопки Просмотр — запускается два раза.
Тут есть специфика построения screensaver. Строго говоря не достаточно просто переименовать exe в scr.
Помимо некоторых правил, то как выход по нажатии любой кнопки, мышки, и запрета на запуск второй копии, ещё
есть некая внутренняя структура, которая управляется с ком. строки(ProgramParameter()), которую нужно правильно обработать:
Вот код с английского форума, демонстрирующий выбор. Т.е система командует параметрами, а ваша программа должна их правильно выполнять! Не система организует Просмотр, а вы должны это действие предусмотреть, как и конфигурирование параметров. Или всё правильно игнорировать, если нет настроек.
В интернете много информации на это счет, разберётесь.
Не критично, StartDrawing вызывается не в главном цикле, а по таймеру.
Но последний StopDrawing() явно лишний!
в строке 1 создаю изображение, начинаю рисование, потом в строке 3 завершаю рисование.а в строке 7 опять начинаю. Но зачем? Чтобы получить что? В хэлпе об этом ничего не сказано (((
в строке 1 создаю изображение, начинаю рисование, потом в строке 3 завершаю рисование.а в строке 7 опять начинаю. Но зачем?
Первое рисование по сути бессмысленно у вас. Там нужно только CreateImage, а Box по таймеру рисуется дальше в коде.
Width, Heght тоже в начале кода уже вычислили, заново нет смысла его вычислять.
Начало может быть таким:
Переписал в отдельные процедуры рисование циферблата и рисование стрелок. Рисование стрелок вызываю по таймеру. Но приходится вызывать и рисование циферблата, и рисование стрелок каждую секунду.
И задумался. А можно ли один раз нарисовать один циферблат, поместить на него мой логотип, а сверху расположить Image с прозрачным фоном и на нём каждую секунду перерисовывать стрелки?
Чтобы каждую секунду не перерисовывать всё окно чтобы стереть старые стрелки (Создание Image, наложение логотипа, рисование циферблата)?
Попытался, но изображение каждую секунду моргает. (((
ну так есть же грабёж, грабим маленький участок под стрелкой и возвращаем его
есть CustomFilterCallback(), что будет проще или удобней в этом случае?, надо пробовать оба варианта и делать свой выбор
стрелки кривоватые, может стрелки вектором рисовать?
если не вектором, то найти как рисуется линия со Зглажыванием

Пользователи опасаются, что из-за задержки с вводом в эксплуатацию нового трубопровода «Газпром» этой зимой не поставит европейским потребителям достаточно топлива.
«Северный поток-2» полностью построен (строительство завершилось в сентябре), и его даже заполнили газом, но эксплуатация газопровода может начаться только после сертификации в Германии. А этот процесс приостановлен, поскольку, согласно немецким законам, оператору предстоит создать и зарегистрировать дочернюю компанию для части линии, работающей в Германии.
Эта операционная компания должна соответствовать немецкому законодательству, прежде чем проект стоимостью 10 миллиардов евро получит сертификацию.
Немецкий регулирующий орган заявил, что процедура сертификации будет приостановлена до тех пор, пока швейцарская материнская компания Nord Stream 2 не передаст основные активы и людские ресурсы своей дочерней компании в Германии, которая владеет и управляет немецкой частью трубопровода.
Решение, вероятно, отложит сертификацию на несколько месяцев, но и это еще не все: «Северный поток-2» должен получить зеленый свет от Европейской комиссии.
Немецкие предприятия вложили значительные средства в трубопровод протяженностью 1225 км, и бывший канцлер Герхард Шредер сыграл большую роль в его развитии.
«Северный поток-2», проходящий по дну Балтийского моря, удвоит экспорт газа из Москвы в Германию, но при этом идет в обход Украины, которая во многом полагается на существующие трубопроводы для получения доходов и сильно пострадает от потери транзитных сборов.
Между тем, по данным немецкого сетевого оператора Gascade, поток российского газа по трубопроводу «Ямал-Европа» (один из основных маршрутов экспорта российского газа в Европу) в Германию в среду утром был стабильным и превысил уровень выходных.
Однако, несмотря на это, декабрьский нейтральный индекс цен на газ виртуальной бирже TTF (Title Transfer Facility), по которому рассчитывается средневзвешенная по объему цена на газ в Европе, к утру среды вырос на 6%.
Правда, рост цен на газ начался не вчера. Холодная зима в Европе в прошлом году отразилась на объемах поставок, и в результате уровень хранимого газа намного ниже обычного.
Политическое оружие
Критики опасаются, что трубопровод увеличит энергетическую зависимость Европы от России.
Украина выступала против «Северного потока-2», который президент Владимир Зеленский назвал «опасным геополитическим оружием».
Многие начинающие пользователи сталкиваются с такой проблемой:
«Прекращена работа программы . «
И многих эта проблема раздражает.
Сейчас я вам расскажу,как справится с этой проблемой.
Подробности
Для начала разберёмся с возможными вариантами,из-за чего эта трабла возникает :
1. Установлено много стороннего ПО,которое «ест» ресурсы системы.
2. Программе не хватает оперативной памяти.
3. В системе не установлено необходимое ПО для «правильной» работы программы.
5. Проблема в самой программе.
6. При запуске программа обращается к какому-нибудь системному файлу,который может быть повреждён.
Теперь пройдёмся по каждому этому варианту:
1. Посмотрите будет ли программа вылетать в режиме «чистой» загрузки ,если в этом режиме всё нормально работает,то попробуем выявит виновника,среди всего установленного ПО, с помощью метода «половинного деления».
Зайдите в Конфигурацию системы -> Службы и включите половину служб и перезагрузитесь. Если проблема не появляется, причина в оставшихся отключенных службах. Если проблема воспроизводится, причина во включенных службах — отключите половину из них и снова перезагрузитесь. Тоже самое и для ПО в Автозагрузке.
2. Убедитесь,что у вас включён файл подкачки,для этого:
а) Нажмите Пуск –> Панель управления –> Система –> Все элементы панели управления –> Дополнительные параметры системы -> Дополнительно:
б) В разделе Быстродействие нажмите Параметр,откройте вкладку Дополнительно и нажмите Изменить;
в) И посмотрите,чтобы стояла галочка напротив надписи «Автоматически выбирать объём файла подкачки».
3. Убедитесь,что у вас установлено следующее ПО:
Для 32 (x86) bit’ных систем :
Для 64 bit’ных систем :
Потом после их установки установите все обновления,которые будут в Центре обновления Windows !
4. Проверьте систему на наличие «зловредов» с помощью Dr.Web CureIt.
5. Проблема может быть в самой программе:
а) Если у вас установлена пиратская версия программы (взломанная , RePack),то обращайтесь к тому,у кого вы ею скачали;
б) Если у вас установлена Beta-версия программы,удалите её и найдите законченную версию программы у разработчика :
в) Если у вас лицензионная версия программы,то обращайтесь в тех. поддержку производителя.
6. Определим,кто виноват в вылете программы,для этого:
а) Скачайте программу ProcDump и распакуйте её в папку C:ProcDump;
б) Откройте командную строку от имени администратора и выполните:
- C:ProcDumpprocdump.exe -accepteula -e -w [имя сбойного приложения] C:ProcDump
в) Как определить имя сбойного приложения:
1) зайдите в Панель управления -> Все элементы панели управления -> Центр поддержки ->Монитор стабильности системы -> Отчеты о проблемах.
2) Найдите событие,когда вылетело проблемное приложение,щёлкните по нему 2 раза левой кнопкой мыши и там вы увидите надпись «Имя приложения:
в) Запустите это приложение и дождитесь вылета.
г) После этого у вас появится файл с расширением .dmp в C:ProcDump
д) Теперь заглянем в это дам (заглядывать в него можно также,как и и в дампы синих экранов Анализ причин возникновения BSOD при помощи Debugging Tools for Windows (только команда выгладит по другому: Kdfe -v [путь к дампу]).
е) Как определите,что за файл виноват — определите системный ли он или принадлежит сторонней программе (для этого достаточно его «погуглить «) ,если к сторонней программе,то определите к какой и удалит её.
Если файл системный,то запустите командную строку от имени администратора и выполните команду:
Дождитесь конца проверки и:
Если в конце проверки будет написано,что все файлы были восстановлены,то перезагрузитесь для их полного восстановления.
Если в конце проверки будет написано,что не все файлы были восстановлены,то:
Если у вас Windows 8/8.1,то вам достаточно в командной строке,запущенной от имени администратора, при подключённом интернете , выполнить команду:
Если у вас Windows 7,то обратимся к другой статье ( пишется ) за помощью.
Читайте также:
- Nvidia process and module monitoring driver что это
- Как сбросить настройки adobe animate
- Программы для телефона самсунг 5230
- Как в ворд пад сделать альбомный лист
- Xerox workcentre 3225 программа для сканирования
Обновлено: 28.01.2023
Критические секции
Критические секции — это объекты, используемые для блокировки доступа всех нитей (threads) приложения, кроме одной, к некоторым важным данным в один момент времени. Например, имеется переменная m_pObject и несколько нитей, вызывающих методы объекта, на который ссылается m_pObject, причем эта переменная может изменять свое значение время от времени. Иногда там даже оказывается нуль. Предположим, имеется вот такой код:
Тут мы имеем потенциальную опасность вызова m_pObject->SomeMethod() после того, как объект был уничтожен при помощи delete m_pObject . Дело в том, что в системах с вытесняющей многозадачностью выполнение любой нити процесса может прерваться в самый неподходящий для нее момент времени, и начнет выполняться совершенно другая нить. В данном примере неподходящим моментом будет тот, в котором нить №1 уже проверила m_pObject , но еще не успела вызвать SomeMethod() . Выполнение нити №1 прервалось, и начала исполняться нить №2. Причем нить №2 успела вызвать деструктор объекта. Что же произойдет, когда нить №1 получит немного процессорного времени и вызовет-таки SomeMethod() у уже несуществующего объекта? Наверняка что-то ужасное.
Именно тут приходят на помощь критические секции. Перепишем наш пример.
Код, помещенный между ::EnterCriticalSection() и ::LeaveCriticalSection() с одной и той же критической секцией в качестве параметра, никогда не будет выполняться параллельно. Это означает, что если нить №1 успела «захватить» критическую секцию m_lockObject, то при попытке нити №2 заполучить эту же критическую секцию в свое единоличное пользование, ее выполнение будет приостановлено до тех пор, пока нить №1 не «отпустит» m_lockObject при помощи вызова ::LeaveCriticalSection(). И наоборот, если нить №2 успела раньше нити №1, то та «подождет», прежде чем начнет работу с m_pObject .
Работа с критическими секциями
Что же происходит внутри критических секций и как они устроены? Прежде всего, следует отметить, что критические секции – это не объекты ядра операционной системы. Практически вся работа с критическими секциями происходит в создавшем их процессе. Из этого следует, что критические секции могут быть использованы только для синхронизации в пределах одного процесса. Теперь рассмотрим критические секции поближе.
Структура RTL_CRITICAL_SECTION
Поле LockCount увеличивается на единицу при каждом вызове ::EnterCriticalSection() и уменьшается при каждом вызове ::LeaveCriticalSection(). Это первая (а часто и единственная проверка) на пути к «захвату» критической секции. Если после увеличения в этом поле находится ноль, это означает, что до этого момента непарных вызовов ::EnterCriticalSection() из других ниток не было. В этом случае можно забрать данные, охраняемые этой критической секцией в монопольное пользование. Таким образом, если критическая секция интенсивно используется не более чем одной нитью, ::EnterCriticalSection() практически вырождается в ++LockCount , а ::LeaveCriticalSection() в —LockCount . Это очень важно. Это означает, что использование многих тысяч критических секций в одном процессе не повлечет значительного расхода ни системных ресурсов, ни процессорного времени.
СОВЕТ
Не стоит экономить на критических секциях. Много cэкономить все равно не получится.
В поле RecursionCount хранится количество повторных вызовов ::EnterCriticalSection() из одной и той же нити. Действительно, если вызвать ::EnterCriticalSection() из одной и той же нити несколько раз, все вызовы будут успешны. Т.е. вот такой код не остановится навечно во втором вызове ::EnterCriticalSection(), а отработает до конца.
Действительно, критические секции предназначены для защиты данных от доступа из нескольких ниток. Многократное использование одной и той же критической секции из одной нити не приведет к ошибке. Это вполне нормальное явление. Следите, чтобы количество вызовов ::EnterCriticalSection() и ::LeaveCriticalSection() совпадало, и все будет хорошо.
Поле OwningThread содержит 0 для никем не занятых критических секций или уникальный идентификатор нити-владельца. Это поле проверяется, если при вызове ::EnterCriticalSection() поле LockCount после увеличения на единицу оказалось больше нуля. Если OwningThread совпадает с уникальным идентификатором текущей нити, то RecursionCount просто увеличивается на единицу и ::EnterCriticalSection() возвращается немедленно. Иначе ::EnterCriticalSection() будет дожидаться, пока нить, владеющая критической секцией, не вызовет ::LeaveCriticalSection() необходимое количество раз.
Поле LockSemaphore используется, если нужно подождать, пока критическая секция освободится. Если LockCount больше нуля, и OwningThread не совпадает с уникальным идентификатором текущей нити, то ждущая нить создает объект ядра (событие) и вызывает ::WaitForSingleObject( LockSemaphore ). Нить-владелец, после уменьшения RecursionCount, проверяет его, и если значение этого поля равно нулю, а LockCount больше нуля, то это значит, что есть как минимум одна нить, ожидающая, пока LockSemaphore не окажется в состоянии «случилось!». Для этого нить-владелец вызывает ::SetEvent(), и какая-то одна ( только одна ) из ожидающих ниток пробуждается и получает доступ к критическим данным.
И, наконец, поле SpinCount . Это поле используется только многопроцессорными системами. В однопроцессорных системах, если критическая секция занята другой нитью, можно только переключить управление на нее и подождать наступления события. В многопроцессорных системах есть альтернатива: прогнать некоторое количество раз холостой цикл, проверяя каждый раз, не освободилась ли наша критическая секция. Если за SpinCount раз это не получилось, переходим к ожиданию. Это гораздо эффективнее, чем переключение на планировщик ядра и обратно. Кроме того, в WindowsNT/2k старший бит этого поля служит для индикации того, что объект ядра, хендл которого находится в поле LockSemaphore, должен быть создан заранее. Если системных ресурсов для этого недостаточно, система сгенерирует исключение, и программа может «урезать» свою функциональность. Или совсем завершить работу.
ПРИМЕЧАНИЕ
Все это верно для Windows NT/2k/XP. В Windows 9x/Me используется только поле LockCount. Там находится указатель на объект ядра, возможно, просто взаимоисключение (mutex). Все остальные поля равны нулю.
API для работы с критическими секциями
BOOL InitializeCriticalSection (LPCRITICAL_SECTION lpCriticalSection );
BOOL InitializeCriticalSectionAndSpinCount (LPCRITICAL_SECTION lpCriticalSection , DWORD dwSpinCount );
Заполняют поля структуры, адресуемой lpCriticalSection. После вызова любой из этих функций критическая секция готова к работе.
DWORD SetCriticalSectionSpinCount (LPCRITICAL_SECTION lpCriticalSection , DWORD dwSpinCount );
Устанавливает значение поля SpinCount и возвращает его предыдущее значение. Напоминаю, что старший бит отвечает за «привязку» события, используемого для ожидания доступа к данной критической секции.
VOID DeleteCriticalSection (LPCRITICAL_SECTION lpCriticalSection );
Освобождает ресурсы, занимаемые критической секцией.
VOID EnterCriticalSection (LPCRITICAL_SECTION lpCriticalSection );
BOOL TryEnterCriticalSection (LPCRITICAL_SECTION lpCriticalSection );
Осуществляют «захват» критической секции. Если критическая секция занята другой нитью, то ::EnterCriticalSection() будет ждать, пока та освободится, а ::TryEnterCriticalSection() вернет FALSE. Отсутствует в Windows 9x/ME.
VOID LeaveCriticalSection (LPCRITICAL_SECTION lpCriticalSection );
Освобождает критическую секцию,
Классы-обертки для критических секций
Классы CLock и CAutoLock удобно использовать для синхронизации доступа к переменным класса, а CScopeLock предназначен, в основном, для использования в процедурах. Удобно, что компилятор сам позаботится о вызове ::LeaveCriticalSection() через деструктор.
Отладка критических секций
Весьма интересное и увлекательное занятие. Можно потратить часы и недели, но так и не найти, где именно возникает проблема. Стоит уделить этому особо пристальное внимание. Ошибки, связанные с критическими секциями, бывают двух типов: ошибки реализации и архитектурные ошибки.
Ошибки, связанные с реализацией
Это довольно легко обнаруживаемые ошибки, как правило, связанные с непарностью вызовов ::EnterCriticalSection() и ::LeaveCriticalSection().
::LeaveCriticalSection() без ::EnterCriticalSection() приведет к тому, что первый же вызов ::EnterCriticalSection() остановит выполнение нити навсегда.
В этом примере, конечно, имеет смысл воспользоваться классом типа CScopeLock.
Кроме того, случается, что ::EnterCriticalSection() вызывается без инициализации критической секции с помощью ::InitializeCriticalSection(). Особенно часто такое случается с проектами, написанными с помощью ATL. Причем в debug-версии все работает замечательно, а release-версия рушится. Это происходит из-за так называемой «минимальной» CRT (_ATL_MIN_CRT), которая не вызывает конструкторы статических объектов (Q166480, Q165076). В ATL версии 7.0 эту проблему решили.
Еще я встречал такую ошибку: программист пользовался классом типа CScopeLock, но для экономии места называл эту переменную одной буквой:
и как-то раз просто пропустил имя у переменной. Получилось
Что это означает? Компилятор честно сделал вызов конструктора CScopeLock и тут же уничтожил этот безымянный объект, как и положено по стандарту. Т.е. сразу же после вызова метода Lock() последовал вызов Unlock(), и синхронизация перестала иметь место. Вообще, давать переменным, даже локальным, имена из одной буквы – путь быстрого наступления на всяческие грабли.
СОВЕТ
Если у вас в процедуре больше одного цикла, то вместо int i,j,k стоит все-таки использовать что-то вроде int nObject, nSection, nRow.
Архитектурные ошибки
Самая известная из них – это взаимоблокировка (deadlock), когда две нити пытаются захватить две или более критических секций, причем делают это в разном порядке.
Проблемы могут возникнуть и при. копировании критических секций. Понятно, что вот такой код вряд ли сможет написать программист в здравом уме и памяти:
Из такого присвоения трудно извлечь какую-либо пользу. А вот такой код иногда пишут:
и все бы хорошо, если бы у структуры SData был конструктор копирования, например такой:
Но нет, программист посчитал, что хватит за глаза простого копирования полей, и, в результате, переменная m_lock была просто скопирована, хотя именно в этот момент из другой нити она была «захвачена», и значение поля LockCount у нее в этот момент больше либо равно нулю. После вызова ::LeaveCriticalSection() в той нити, у исходной переменной m_lock значение поля LockCount уменьшилось на единицу. А у скопированной переменной – осталось прежним. И любой вызов ::EnterCriticalSection() в этой нити никогда не вернется. Он будет вечно ждать неизвестно чего.
Это только цветочки. С ягодками вы очень быстро столкнетесь, если попытаетесь написать что-нибудь действительно сложное. Например, ActiveX-объект в многопоточном подразделении (MTA), создаваемый из скрипта, запущенного из-под контейнера, размещенного в однопоточном подразделении (STA). Ни слова не понятно? Не беда. Сейчас я попытаюсь выразить проблему более понятным языком. Итак. Имеется объект, вызывающий методы другого объекта, причем живут они в разных нитях. Вызовы производятся синхронно. Т.е. объект №1 переключает выполнение на нить объекта №2, вызывает метод и переключается обратно на свою нить. При этом выполнение нити №1 приостановлено до тех пор, пока не отработает нить объекта №2. Теперь, положим, объект №2 вызывает метод объекта №1 из своей нити. Получается, что управление вернулось в объект №1, но из нити объекта №2. Если объект №1 вызывал метод объекта №2, захватив какую-либо критическую секцию, то при вызове метода объекта №1 тот заблокирует сам себя при повторном входе в ту же критическую секцию.
Если бы в примере не было переключения нитей, все вызовы произошли бы в нити объекта №1, и никаких проблем не возникло. Сильно надуманный пример? Ничуть. Именно переключение ниток лежит в основе подразделений (apartments) COM. А из этого следует одно очень, очень неприятное правило.
СОВЕТ
Избегайте вызовов каких бы то ни было объектов при захваченных критических секциях.
Помните пример из начала статьи? Так вот, он абсолютно неприемлем в подобных случаях. Его придется переделать на что-то вроде примера, приведенного в листинге 12.
Доступ к объекту по-прежнему синхронизован, но вызов SomeMethod(); происходит вне критической секции. Победа? Почти. Осталась одна маленькая деталь. Давайте посмотрим, что происходит в Proc2():
Очевидно, что вызовы m_pObject.p->AddRef(); и m_pObject.p->Release(); происходят внутри критической секции. И если вызов метода AddRef(), как правило, безвреден, то вызов метода Release() может оказаться последним вызовом Release(), и объект самоуничтожится. В методе FinalRelease() объекта №2 может быть все что угодно, например, освобождение объектов, живущих в других подразделениях. А это опять приведет к переключению ниток и может вызвать самоблокировку объекта №1 по уже известному сценарию. Придется воспользоваться той же техникой, что и в методе Proc1():
Теперь потенциально последний вызов IObject2::Release() будет осуществлен после выхода из критической секции. А присвоение нового значения по-прежнему синхронизовано с вызовом IObject2::SomeMethod() из нити №1.
Способы обнаружения ошибок
Сначала стоит обратить внимание на «официальный» способ обнаружения блокировок. Если бы кроме ::EnterCriticalSection() и ::TryEnterCtiticalSection() существовал еще и ::EnterCriticalSectionWithTimeout(), то достаточно было бы просто указать какое-нибудь резонное значение для интервала ожидания, например, 30 секунд. Если критическая секция не освободилась в течение указанного времени, то с очень большой вероятностью она не освободится никогда. Имеет смысл подключить отладчик и посмотреть, что же творится в соседних нитях. Но увы. Никаких ::EnterCriticalSectionWithTimeout() в Win32 не предусмотрено. Вместо этого есть поле CriticalSectionDefaultTimeout в структуре IMAGE_LOAD_CONFIG_DIRECTORY32 , которое всегда равно нулю и, судя по всему, не используется. Зато используется ключ в реестре «HKLMSYSTEMCurrentControlSetControlSession ManagerCriticalSectionTimeout», который по умолчанию равен 30 суткам, и по истечению этого времени в системный лог попадает строка «RTL: Enter Critical Section Timeout (2 minutes)nRTL: Pid.Tid XXXX.YYYY, owner tid ZZZZnRTL: Re-Waitingn». К тому же это верно только для систем WindowsNT/2k/XP и только с CheckedBuild . У вас установлен CheckedBuild ? Нет? А зря. Вы теряете исключительную возможность увидеть эту замечательную строку.
Ну и заодно добавим еще один метод в наш класс Clock (листинг 15).
Использовать метод Check() в release-конфигурациях не стоит, возможно, что в будущем, в какой-нибудь Windows64, структура RTL_CRITICAL_SECTION изменится, и результат такой проверки будет не определен. Так что ему самое место «жить» внутри всяческих ASSERT’ов.
Итак, что мы имеем? Мы имеем проверку на лишний вызов ::LeaveCriticalSection() и ту же трассировку для блокировок. Не так уж много. Особенно если трассировка о блокировке имеет место, а вот нить, забывшая освободить критическую секцию, давно завершилась. Как быть? Вернее, что бы еще придумать, чтобы ошибку проще было выявить? Как минимум, прикрутить сюда __LINE__ и __FILE__, константы, соответствующие текущей строке и имени файла на момент компиляции этого метода.
Приводим наши классы в соответствие (листинг 17).
К сожалению, пришлось даже переопределить CScopeLock lock(cs), причем жестко привязаться к имени переменной. Не стоит говорить о том, что наверняка получился конфликт имен — все-таки Lock довольно популярное название для метода. Такой код не будет собираться, например, с популярнейшей библиотекой ATL. Тут есть два способа. Переименовать методы Lock() и TryLock() во что-нибудь более уникальное, либо переименовать Lock() в ATL:
Сменим тему
А что это мы все про Win32 API да про C++? Давайте посмотрим, как обстоят дела с критическими секциями в более современных языках программирования.
Тут стараниями Майкрософт имеется полный набор старого доброго API под новыми именами.
В этом языке используется подобный механизм, только место ключевого слова lock есть ключевое слово synchronized , а все остальное – точно так же.
MC++ (управляемый C++)
Тут тоже появился атрибут [synchronized] ведущий себя точно так же, как и одноименное ключевое слово из Java. Странно, что архитекторы из Майкрософт решили позаимствовать синтаксис из продукта от Sun Microsystems вместо своего собственного.
Delphi
Практически все, что верно для C++, верно и для Delphi. Критические секции представлены объектом TCriticalSection . Собственно, это такая же обертка как и наш класс CLock.
Кроме того, в Delphi присутствует специальный объект TMultiReadExclusiveWriteSynchronizer с названием, говорящим само за себя.
Рассмотрим ситуацию, при которой необходимо иметь совместный доступ к ресурсу (порт, область памяти, какая-либо переменная) да ещё так, чтобы в единицу времени только один таск мог обращаться к данному ресурсу, в то время как остальные задачи, желающие получить доступ к ресурсу должны ждать своей очереди.
На помощь приходит специальный тип семафора — мьютекс(mutex). Данная абстракция представляет собой своеобразный жетон, имея который, таск может иметь доступ к ресурсу, и который необходимо вернуть по окончанию работы с ресурсом, причем вернуть «жетон» может только тот таск, который его взял.
Данную логику иллюстрируют следующие изображения:




Рисунок 4. После окончания работы таск В возвращает жетон и в результате другие таски могут получить доступ к ресурсу.
Для создания мьютекса используется специальная API функция:
Которая возвращает созданный мьютекс, или NULL, если недостаточно памяти.
Следующий код показывает механизм работы с мьютексом:
Критические секции.
Что если по ходу работы логики Вашего кода, необходим короткий участок кода, выполнение которого не должно быть прервано переключением на другой таск (примером такого участка может послужить запись какого-либо значения в порт). Для этого в FreeRTOS используются специальные макросы:
Во время выполнения этого участка кода не может быть произведено переключение контекста (т.е. переключение на другую задачу), но могут приходить прерывания на версиях FreeRTOS с поддержкой вложенных прерываний, но не все, а только те у которых приоритет выше константы configMAX_SYSCALL_INTERRUPT_PRIORITY.
Другой подход построения критических секций — приостановка работы планировщика. Разница лишь в том, что критическая секция, построенная на макросах, защищает выполняемый участок кода от совместного доступа других тасков, и прерываний, а критическая секция, построенная на приостановке планировщика — только от других тасков.
Для этого используют 2 API функции:
При этом значение, которое возвращает функция xTaskResumeAll(); указывает на необходимость выполнения принудительного переключения контекста с помощью taskYIELD();
Инверсия приоритетов и тупики.
В прошлой статье я вскользь упомянул о gatekeeper task, как о решении проблемы инверсии приоритетов и попадания тасков в тупик. Здесь я бы более подробно хотел описать, что это такое.
Инверсия приоритетов — это ситуация при которой, Task A имеющий более высокий приоритет, чем Task B ожидает завершения его работы т.к. он первым захватил „жетон» (мьютекс).
- Task A выполняется и захватывает «жетон» X.
- Выполнение работы Task A прерывается Task B.
- Task B захватывает «жетон» Y, перед этим пытаясь захватить «жетон» X, который сейчас используется Task A. В результате Task B впадает в ожидание Task A.
- Task A продолжает выполняться, и хочет захватить «жетон» Y, который используется Task B и в результате тоже впадает в ожидание.
Пожалуй, на этом все, интересно было бы услышать поправки по всему циклу, дабы скорректировать статьи, и в дальнейшем ссылаться на них.
Для дальнейшего изучения я бы, прежде всего, посоветовал прочитать оригинальное руководство «Using the FreeRTOS real time kernel», информация из которого использовалась при написании статей, а также документацию на официальном сайте.
Программа FreeReason не требует установки. Для ее работы необходимо созать папку и скопировать туда содержимое архива. Два файла fr.exe и frall.dll.
Для правильного отображения некоторых условных знаков в туже папку нужно распаковать архив со значками EMF.
Установка необходима драйверам аппаратных ключей защиты которые можно взять со страницы загрузки драйверов фирмы Guardant.
FreeAdjust также не требует установки. Необходимо распакавать архив FreeAdjust в любом месте на жеском диске вашего копьютера. Исходный код под Qt находится в архиве fa_src.zip
- Добавлены новые форматы импорта XML
- облегченная
- полная
- облегченная
- полная
- Изменены пути доступа к картам и космоснимкам Yandex
- облегченная
- полная
- облегченная
- полная
- Добавлены новые форматы XML файлов
- Добавлен экспорт приращений в геодезических измерениях
- Обновлен путь доступа к сайту кадастровых карт росреестра
- облегченная
- полная
- облегченная
- полная
- Исправлены ошибки обнаруженые при импорте XML файлов
- Добавлен импорт нового тега extract_base_params_land
- облегченная
- полная
- облегченная
- полная
- Реализована загрузка формата выписок по территориальным зонам extract_about_zone
- Реализована загрузка формата выписок по строениям KVOKS
- Изменены настройки компилятора приводившие к сбоям программы.
- Реализована загрузка нового формата XML выписок по кадастровому кварталу от кадастровой палаты (сделано по имеющимся выпискам. Не реализованы единые землепользования т.к. небыло примеров)
- Реализована загрузка нового формата XML выписок по кадастровому кварталу от кадастровой палаты (сделано по имеющимся выпискам. Не реализованы единые землепользования т.к. небыло примеров)
У меня есть многопоточная программа Python (финансовая торговля), в которой определенные потоки выполняют критические разделы (например, в середине выполнения сделки). Поток, выполняющий критические разделы, является потоком демона. Основной поток программы захватывает SIGINT и пытается корректно выйти из программы, освобождая все ресурсы, удерживаемые дочерними потоками. Чтобы не допустить, чтобы основной поток внезапно завершал дочерние потоки; основной поток будет перебирать список дочерних объектов потока и вызывать их функцию shutdown() . Эта функция будет блокироваться до завершения критического участка потока перед возвратом.
Ниже приводится базовая реализация.
Я не уверен, будет ли моя реализация работать, потому что, пока ChildDaemonThread выполняет критический раздел через do_critical_stuff() , если родительский поток вызывает дочерний shutdown() , который блокируется до выполнения критического раздела, то на этом этапе используются два метода ChildDaemonThread run() и do_critical_stuff() . звонили в то же время (я не уверен, законно ли это вообще). Это возможно? Моя реализация верна? Есть ли лучший способ добиться этого?
В этой статье мы расскажем о хитростях и советах по Python, которые должны быть известны разработчику Python.
В одном из недавних постов я рассказал о том, как я использую навыки количественных исследований, которые я совершенствую в рамках программы TPQ.
Вы когда-нибудь хотели поделиться с кем-то файлом, но он содержал конфиденциальную информацию? Многие думают, что электронная почта безопасна, но это.
Недавно я столкнулся с интересной бизнес-задачей — визуализацией сбоев в цепочке поставок лекарств, которую могут просматривать врачи и.
Ответы 1
В этой реализации есть некоторые состояния гонки.
У вас нет гарантии, что основной поток проверит значение _critical_section в нужный момент, чтобы увидеть значение False . Рабочий поток может покинуть критическую секцию и снова войти в нее до того, как основной поток снова сможет проверить значение. Это может не вызывать проблем с корректностью, но может привести к тому, что ваша программа будет дольше завершать работу (поскольку, когда основной поток «пропускает» безопасное время для завершения, ему придется ждать завершения другого критического раздела).
Кроме того, рабочий поток может повторно войти стать критическим после того, как основной поток заметил, что _critical_section — это False , но до того, как основной поток успеет вызвать завершение процесса. Это может вызвать серьезные проблемы с правильностью, поскольку это фактически мешает вашей попытке убедиться, что критический раздел завершен.
Конечно, программа также может аварийно завершить работу из-за какой-либо другой проблемы. Поэтому может быть лучше, если вы реализуете возможность восстановления после прерванного критического раздела.
Однако, если вы хотите максимально улучшить эту стратегию, я бы предложил нечто подобное:
Ключевым моментом здесь является то, что join будет блокироваться до завершения потока. Это гарантирует, что вы не прервете ни одной средней критической секции.
Привет, большое спасибо. Ваш ответ имеет большой смысл. Я думал, что вместо использования флага Boolean _keep_running я мог бы вам использовать экземпляр Event() , который является поточно-ориентированным. Что вы скажете по этому поводу?
Я не считать, вам нужен Event в этом случае. Установка атрибута в Python является потокобезопасной (один поток может выполнять self._keep_running = False , а другой поток читает self._keep_running ; в этом случае вы никогда не получите противоречивый результат). Это правда, что что-то вроде Event может быть полезно (и часто нужно) в определенных сценариях многопоточности. Но . не этот, я думаю. Я могу быть не прав. Рассуждения о многопоточности — непростая задача.
Досрочная остановка потока
В некоторых ситуациях одному потоку может потребоваться уведомить
другой поток о своем завершении. Это обычно происходит, когда поток
выполняет длительную операцию, и пользователь решает выйти из
приложения, или операция должна быть прервана. TThread обеспечивает
простой механизм для поддержки таких действий, а именно, метод
Terminate и свойство Terminated. Когда поток создается, свойство
Terminated установлено в False, а всякий раз, когда вызывается метод
Terminate, свойство Terminated для этого потока устанавливается в True.
Таким образом, на всех потоках лежит ответственность за периодическую
проверку, не были ли они остановлены, и если это случается, за
корректное завершение своей работы. Заметьте, что никакой
крупномасштабной синхронизации при этом не происходит: когда один
поток устанавливает свойство Terminated другого, нельзя предполагать,
что другой поток тут же прочитает значение своего свойства Terminated и
начнет процесс завершения. Свойство Terminated является просто флагом,
говорящим “пожалуйста, завершайся как можно скорее”.
В коде потока мы пишем что-то такое…
Событие OnTerminate

Событие OnTerminate происходит, когда поток в самом деле завершается.
Оно не случается, когда вызывается метод потока Terminate. Это событие
может быть весьма полезным, поскольку оно выполняется в контексте
основного потока VCL, подобно методам, вызываемым с помощью
Synchronize. Таким образом, если есть желание выполнять какие-то
действия VCL с потоком, который автоматически освобождается по
окончании, то обработчик этого события – прекрасное место для таких
действий. Для большинства программистов, начинающих работать с
потоками, это наиболее удобный путь получения данных из не-VCL потока
без особых усилий, не требующий явных вызовов синхронизации.Как можно видеть на диаграмме, OnTerminate работает в основном так же,
как и Synchronize, и семантически это почти идентично вызову Synchronize
в конце потока. Основная польза от такой ситуации заключается в том, что
используя флаг, например, “AppCanQuit” или счетчик работающих потоков
в главном потоке VCL, можно обеспечить простые механизмы проверки
того, что основной поток VCL завершается только тогда, когда все другие
потоки остановлены.
Читайте также:
- Завис 1с удаленно что делать
- Фотошоп на море сделать себя
- Какие типы памяти доступны для программиста при разработке программ
- Не воспроизводится видео в браузере опера
- Эксель поиск в другом файле
-
Задача: есть поток1 и поток2, которые работают по следующей схеме:
…есть переменная Ready=0
…поток1 стартует поток2 и засыпает пока Ready не будет 1
…поток2 нормально работает, инициализирует некоторую крит. секцию, после инициализации делает Ready=1
…поток2 крутится в вечном цикле, каждая итерация которого обёрнута в EnterCriticalSection+LeaveCriticalSection
…вдруг поток1 просыпается (т.к Ready=1) и вызывает свою процедуру, обёрнутую в EnterCriticalSection+LeaveCriticalSection с той же к/с, что и поток2
…поток2 на очередном витке цикла пытается сделать EnterCriticalSection и умирает (этого быть не должно).Описание ошибки: Access Violation: Write on Address 00000000. GetLastError и try+except не работают, т.к. Винда СРАЗУ ЖЕ убивает приложение без вопросов.
Попытался логировать события (записывал содержимое структуры RTL_CRITICAL_SECTION):
-
9:19:29 … before enter: DebugInfo = 14C250 LockCount = -1 RecursionCount = 0 OwningThread = 0 LockSemaphore = 0 Reserved = 0
-
9:19:29 … after enter : DebugInfo = 14C250 LockCount = 0 RecursionCount = 1 OwningThread = 5A4 LockSemaphore = 0 Reserved = 0
-
9:19:29 … before leave: DebugInfo = 14C250 LockCount = 0 RecursionCount = 1 OwningThread = 5A4 LockSemaphore = 0 Reserved = 0
-
9:19:29 … after leave : DebugInfo = 14C250 LockCount = -1 RecursionCount = 0 OwningThread = 0 LockSemaphore = 0 Reserved = 0
-
9:19:29 … before enter: DebugInfo = 14C250 LockCount = -1 RecursionCount = 0 OwningThread = 0 LockSemaphore = 0 Reserved = 0
-
9:19:29 … after enter : DebugInfo = 14C250 LockCount = 0 RecursionCount = 1 OwningThread = 5A4 LockSemaphore = 0 Reserved = 0
-
9:19:29 … before leave: DebugInfo = 14C250 LockCount = 0 RecursionCount = 1 OwningThread = 5A4 LockSemaphore = 0 Reserved = 0
-
9:19:29 … after leave : DebugInfo = 14C250 LockCount = -1 RecursionCount = 0 OwningThread = 0 LockSemaphore = 0 Reserved = 0
-
9:19:29 … before enter: DebugInfo = 14C250 LockCount = -1 RecursionCount = 0 OwningThread = 0 LockSemaphore = 0 Reserved = 0
-
9:19:29 … after enter : DebugInfo = 14C250 LockCount = 0 RecursionCount = 1 OwningThread = 5A4 LockSemaphore = 0 Reserved = 0
-
9:19:29 … before leave: DebugInfo = 14C250 LockCount = 0 RecursionCount = 1 OwningThread = 5A4 LockSemaphore = 0 Reserved = 0
-
9:19:29 … after leave : DebugInfo = 14C250 LockCount = -1 RecursionCount = 0 OwningThread = 0 LockSemaphore = 0 Reserved = 0
-
9:19:29 … before enter: DebugInfo = 14C250 LockCount = -1 RecursionCount = 0 OwningThread = 0 LockSemaphore = 0 Reserved = 0
-
9:19:29 … after enter : DebugInfo = 14C250 LockCount = 0 RecursionCount = 1 OwningThread = 5A4 LockSemaphore = 0 Reserved = 0
-
9:19:29 … before enter: DebugInfo = 14C250 LockCount = 0 RecursionCount = 1 OwningThread = 5A4 LockSemaphore = 0 Reserved = 0
-
9:19:29 … before leave: DebugInfo = 14C250 LockCount = 1 RecursionCount = 1 OwningThread = 5A4 LockSemaphore = 764 Reserved = 0
-
9:19:29 … after enter : DebugInfo = 14C250 LockCount = 0 RecursionCount = 1 OwningThread = AA0 LockSemaphore = 764 Reserved = 0
-
9:19:29 … after leave : DebugInfo = 14C250 LockCount = 0 RecursionCount = 1 OwningThread = AA0 LockSemaphore = 764 Reserved = 0
-
9:19:29 … before enter: DebugInfo = 14C250 LockCount = 0 RecursionCount = 1 OwningThread = AA0 LockSemaphore = 764 Reserved = 0
(before enter и after enter — вызываются соответственно перед и после EnterCriticalSection, before leave и after leave — аналогично для LeaveCriticalSection).
Видно, что поток1 ещё не вышел из к/с, как в неё попытался зайти поток2. before enter поток2 вызвал, а вот after enter — уже нет (приложение завершилось).Вопрос1: почему поток2 вместо ожидания умирает?
Вопрос2: чем это лечится? -
-

nester7
New Member
- Публикаций:
-
0
- Регистрация:
- 5 дек 2003
- Сообщения:
- 720
- Адрес:
- Russia
-
Мало поможет, т.к.:
1) Delphi
2) кода многоВ общем можно смотреть схему работы и лог в посте №#1. Ответы на основные возможные вопросы:
…InitializeCriticalSection делаю 1 раз (на старте 2го потока, первый в это время спит).
…Все вызовы EnterCriticalSection и LeaveCriticalSection парные — пока 1ый поток не войдёт в к/с, всё просто супер.
…Оба потока в одном процессе =).
…Ни один из потоков не может войти в к/с 2 раза подряд.
…Других синхронизирующих примитивов / общих переменных у них нет.З.Ы.: если что — смотрите исходники, но я думаю, что это не метод…
-

leo
Active Member
- Публикаций:
-
0
- Регистрация:
- 4 авг 2004
- Сообщения:
- 2.542
- Адрес:
- Russia
LordBublicXIII
Скорее всего умирает основной поток, поскольку в dpr нет tryexcept (что весьма странно, т.к. в вызываемых функциях LoadResource и Resources[0]=GetRes предусмотрена генерация исключений). Копаться в этой «корявой путаннице» лень, но похоже у тебя просто Path не инициализирована — вот на LoadResource или Resources[0] и валится
PS: нафига выносить инициализацию в отд.модуль, чтобы потом забыть ее вызвать ?!
-
leo,
спасибо что дал мне пинка под зад! Я нашёл косяк — в LoadResource вместонадо было поставить
(иначе при первом же обращении получается выход за границы массива). Вся фишка была в том, что Delphi при отладке многопоточного приложения многократно кидала меня из потока в поток, а ошибка вылетела не в том потоке, который реально вызвал ошибку, а в том, который в этот момент пытался в лезть в крит. секцию.
Прошу прощения, господа, что отнял ваше драгоценное время по своей тупости. Если модерам не сложно, тему можно удалить ФПЕНЬ!
-

leo
Active Member
- Публикаций:
-
0
- Регистрация:
- 4 авг 2004
- Сообщения:
- 2.542
- Адрес:
- Russia
LordBublicXIII
Не поленился пройтись отладчиком. Так и есть — элементарная ошибка в LoadResource:-
if l < idx then //!!! должно быть <=, иначе ResList = Nil и кранты 🙂
PS: Прежде чем строить хитроумные версии, нужно элементарно пройтись отладчиком и посмотреть где валится…
PS: Эх, не успел…
-

leo
Active Member
- Публикаций:
-
0
- Регистрация:
- 4 авг 2004
- Сообщения:
- 2.542
- Адрес:
- Russia
LordBublicXIII
В сообщении об ошибке указывается ее адрес — запускаешь прогу заново, переходишь по этому адресу и смотришь в чем м.б. дело
Добро пожаловать!
Войдите или зарегистрируйтесь сейчас!
Войти
Страница 2 из 2
-

Форумчанин
- Регистрация:
- 23 июл 2013
- Сообщения:
- 43
- Симпатии:
- 5
- Адрес:
-
Геленджик
Звонил 2 раза и никто не ответил — просто тел не работал. Куда вы подевались?..
#21
-

- Регистрация:
- 21 янв 2016
- Сообщения:
- 4
- Симпатии:
- 0
Здравствуйте, мы геодезисты из Тольятти, у нас возникла необходимость перевести съемку из Автокада в freereason, возможно ли это сделать без покупки оф. версии программы? Может существует демо-версия или тому подобное? Работа разовая, для постоянного пользования нам программа не нужна.
#22
-

- Регистрация:
- 24 сен 2013
- Сообщения:
- 4
- Симпатии:
- 0
А можно ли применять коды в программе?
#23
-

Форумчанин
скинь файлы вличку — переведу в acad
#24
-

- Регистрация:
- 21 янв 2016
- Сообщения:
- 4
- Симпатии:
- 0
У нас есть топографический план, возможно ли будет его перевести в freereason из автокада и в каком виде он там будет? Сохраняться ли типы линий и шрифты? И возможно ли подгружить ПДФ в freereason?
#25
-

Форумчанин
в Fr можно подгрузить автокадовский чертеж как подложку (есть возможность импортиромать чертеж по слоям, но это слишком муторно когда много слоев), pdf только подложка
#26
-

Форумчанин
- Регистрация:
- 23 июл 2013
- Сообщения:
- 43
- Симпатии:
- 5
- Адрес:
-
Геленджик
Можно. Там есть встроенный язык программирования и даже не один.
#27
-

- Регистрация:
- 21 янв 2016
- Сообщения:
- 4
- Симпатии:
- 0
Нам нужно перевести этот файл в freereason, но еще нам нужно посмотреть как там отображается эта съемка. Можно ли сделать скрин или ПДФ того, как получилась переведенная съемка в freereason? Заранее благодарим.
Вложения:
#28
-

Форумчанин
Можно из FR в AutoCAD, импорт DXF в fr займет много времени
#29
-

- Регистрация:
- 21 янв 2016
- Сообщения:
- 4
- Симпатии:
- 0
Нам надо из автокада в freereason. Можем скинуть этот файл в DXF.
#30
-

Форумчанин
Эт я понял
Импорт DXF в fr займет много времени, в FR проще будет нарисовать заново!#31
-

Форумчанин
Всем Доброго времени суток! Столкнулся тоже с такой проблемкой. В Краснодарском Крае ведут ИСОГД в freereason. Мы с такой программой никогда не сталкивались. Сами работаем в Digitals, а потом экспортируем в dwg или dxf. Наша программа экспорт в формат dbi и dbs не делает. Может кто поможет конвертировать dwg или dxf в формат dbi и dbs? Объект одноразовый, поэтому покупать прогу или учиться ею пользоваться не вариант. Спасибо за помощь!
#32
-

Форумчанин
- Регистрация:
- 23 июл 2013
- Сообщения:
- 43
- Симпатии:
- 5
- Адрес:
-
Геленджик
Можно попробовать. Скиньте материал.
А еще по этому вопросу поговорите с Сергеем по тел +79528234002#33
-

Форумчанин
Благодарю за помощь. А на какую почту скинуть?
#34
-

- Регистрация:
- 14 фев 2017
- Сообщения:
- 1
- Симпатии:
- 0
- Адрес:
-
г. Геленджик
FreeReason СУБД, не все алгоритмы триангуяции корректно работают, (см. CREDO)
#35
Страница 2 из 2
Поделиться этой страницей
Сегодняшняя статья будет посвящена нескольким подходам к отладке зависаний программы.
Состоит она из шести частей:
— Подготовка — общие действия для всех случаев отладки.
— Delphi — отладка зависания в Delphi.
— EurekaLog — поиск причины зависания в EurekaLog.
— Process Explorer — поиск причины зависания утилитой Process Explorer.
— Threads Snapshot — поиск причины зависания утилитой Threads Snapshot.
— Практический пример — пример с искусственным зависанием в программе.
Подготовка
Ну, прежде чем приступать к отладке программы, вам надо бы её пересобрать (делайте Build, а не просто Compile), добавив в неё отладочную информацию. Для разных подходов требуется разные форматы отладочной информации, поэтому лучше включить всё сразу, чтобы не думать 🙂 Это необходимо сделать, чтобы отладочные средства могли показывать вам читабельные названия процедур и номера строк в исходниках. В противном случае, вам придётся иметь дело с машинным кодом и смещениями.
Итак, вам нужно включить (Project/Options/Linking): «MAP file» — Detailed, «Debug Information» (в старых версиях Delphi она называлась «Include TD32 debug info») — True, «Include remote debug symbols» — True:

Кроме отладочной информации, полезно включить опции «Stack Frames» и «Use Debug DCUs» на вкладке «Compiling»:

Уж позвольте мне не повторяться, что делают эти опции.
Не забываем сделать Build после изменения опций. Понятно, что если у вас несколько проектов (DLL, BPL и т.п.), то менять опции и пересобирать надо все. Также, если вы используете сборку с run-time пакетами — по возможности, выключите их на время тестирования, ибо пакеты сильно всё усложняют.
Delphi
Вы можете подумать: «но я не могу воспроизвести это под отладчиком!» или «на проблемной машине у меня не стоит Delphi!». Но не спешите переходить к следующему разделу.
Во-первых, вам необязательно запускать программу в Delphi. Вы можете запустить программу как обычно, вне среды, и работать с ней, пока она не зависнет — после чего подключить к ней отладчик. Во-вторых, если на машине не стоит Delphi — вы можете установить на неё удалённый отладчик. Подробнее об удалённом отладчике — см. справку или мою статью (большой объём, вот вариант в PDF) — раздел 2, в конце секции 2.1.1. про работу с отладчиком.
Для отладки зависшего проекта в Delphi его, конечно же, нужно открыть. Предварительно он должен быть скомпилирован — с установленными опциями отладки, как мы сделали выше. Кроме того, для локальной машины желательно, чтобы вы запускали программу из Output path, указанном в опциях проекта (т.е. не перемещали бы скомпилированный exe перед запуском).
Итак, вы запустили свою программу, она сколько-то там поработала и зависла. Открываем Delphi, загружаем нужный проект и делаем Run/Attach to process:

Из списка выбираем вашу программу (введя при необходимости имя удалённой машины, если Delphi и программа находятся на разных машинах), устанавливаем галочку «Pause after attach» и жмём «Attach».
Отладчик подключится к процессу и установит его на паузу — путём возбуждения точки останова в потоке отладчика:

Вы можете игнорировать поток отладчика — просто перейдите на окно Threads и выберите любой свой поток для его отладки. Вы можете просмотреть стек вызовов, переключаться между потоками, анализировать переменные, делать пошаговое выполнение и т.п. — в общем, пользоваться отладчиком Delphi как обычно. Если вы включали отладочную информацию, то вы можете закрыть окно CPU и пользоваться отладкой по исходному тексту.
Напомню, что при возможности отладку зависаний стоит производить в ОС Vista и выше — потому что в этих системах появилась новая возможность для отладчиком: Wait Chain Traversal. Отладчик Delphi последних версий поддерживает WCT. Поэтому, если вы используете BDS 2009 или выше и Windows Vista или выше, то в окне Threads напротив каждого потока в колонке «Wait Chain» можно увидеть его статус, чего он ждёт, есть ли взаимоблокировка и т.п.:

EurekaLog
В EurekaLog есть фишка «Anti-Freeze». Собственно, её я уже разбирал в отдельной статье, поэтому не буду останавливаться сейчас — у нас и так сегодня куча материала.
Кратко скажу: это — удобная возможность, когда она применима. Но вы не можете использовать её, если ваш главный поток не занят выборкой сообщений (т.е. в консольных приложениях и службах). В результате применения вы получите стек вызовов зависшего потока и сможете проанализировать ситуацию. Это удобно для сообщения вам о проблемах на клиентских машинах, где у вас нет не то что отладчика, но часто вы даже можете не знать, что программа у кого-то виснет. Для отладки же — намного удобнее использовать Delphi, как мы только что разбирали.
Process Explorer
Если же по каким-то причинам среда Delphi для вас недоступна — вам придётся производить отладку руками.
Перед тем, как производить отладку, надо подготовить Process Explorer. Здесь будет два шага — оба опциональных, но для максимального удобства лучше сделать оба.
Шаг первый — настроить Process Explorer на загрузку отладочных символов. Дело в том, что Windows поставляется с обычными исполняемыми модулями без отладочной информации. Для своих программ мы только что включили генерацию отладочной информации (см. первый пункт), то как мы можем сделать это для Windows, чьи исходники нам недоступны? Ну, Microsoft позаботилась об этом: она распространяет отладочную информацию для своих программ отдельно. Вы можете скачать её и получить читабельные стеки вызовов.
Для начала вам понадобится скачать и установить Windows Debugging Tools. Затем, вам нужно решить, качать ли всю отладочную информацию скопом или же пусть она качается по запросу отладочной программы. Если вы выбрали первый путь — то вперёд, качаем и устанавливаем. Лично я выбираю второй путь.
Для второго способа вам нужно создать папку на своей машине с правом чтения-записи файлов и папок в ней. После чего остаётся только настроить Process Explorer:

В первом поле указывается путь к DbgHelp.dll — если вы устанавливали Windows Debugging Tools, то берите библиотеку оттуда. Если нет — то берите C:WindowsSystem32dbghelp.dll (однако я не уверен, будет ли это работать).
Во втором поле указывается сразу две вещи: первая — ваша общая папка, где вы хотите складировать отладочную информацию. Вторая — сервер отладочной информации, который выдаёт её по запросу программы. Как видите, я складываю информацию в папку C:ProgramDataDebugSymbols (я добавил права на запись для пользователей в этой папке), а беру её со стандартного сервера Microsoft https://msdl.microsoft.com/download/symbols.
После того, как вы это настроили, Process Explorer будет пытаться получить отладочную информацию о каждом необходимом файле и кэшировать её в указанной папке. Поэтому, когда вы просматриваете потоки процесса или их стек вызовов, вы можете иногда видеть надпись «Loading symbols for ABC.exe+0xXYZ…». В общем, после этого вам станет доступно больше информации для системных модулей.
Второй момент, который нужно сделать — сконвертировать map-файл вашего проекта в формат, понимаемый Process Explorer. Дело в том, что Delphi создаёт только различные Borland-ские форматы отладочной информации, а Process Explorer, как утилита Microsoft, понимает только Microsoft-ские форматы отладочной информации. Я уже говорил об этом. Сделать это можно утилитой map2dbg. Это простая консольная утилитка. Качаете архив, распаковываете, открываете консоль и пишете:
map2dbg Project1.exe
По файлам Project1.exe и Project1.map утилита сделает вам файл Project1.dbg, который может быть использован в Microsoft-ских утилитах.
Что-ж, я тут много чего сказал. Давайте я продемонстрирую, что вы получаете, выполнив указанные выше вещи. Ниже — три скриншота. Слева направо: вид стека вызовов без выполнения обоих пунктов (т.е. без системной и без проектной отладочной информацией), вид стека вызовов с первым пунктом (с системной, но без проектной отладочной информацией) и вид стека вызовов с обоими пунктами (с системной и с проектной отладочной информацией):



Как видите, подключение отладочной информации даёт вам три вещи:
— Более читабельный стек вызовов (вместо имя-модуля+смещение вы получаете имя-модуля!процедура+смещение)
— Более полный стек вызовов (без отладочной информации эвристика трассировки стека может опускать вызовы)
— Более правдивый стек вызовов (анализатор может неверно определять имя функции, если идёт вызов внутренней функции, которая не имеет публичного имени, но рядом находится другая функция, которая как-раз таки имеет публичное имя — поэтому анализатор может посчитать внутреннюю функцию частью публичной)
Если вы не будете (или не сможете) подключать отладочную информацию — вам придётся работать со смещениями. Вы конечно, можете искать смещения в map-файле руками, но это весьма хлопотно. Гораздо проще просто выписать на бумажку все числа, приписав имя модуля. Затем запускаете проект у себя, ставите его на паузу и используете команду Search/Goto address. Вводите адрес — и Delphi переводит вас на строчку в исходном тексте, а если это невозможно — то открывает окно CPU. Какой вводить адрес? Два примера. Вы выписали Project1.exe + $1234 и Project2.dll + $4321. Базовый адрес exe обычно не меняется и равен $400000. Вы загрузили проект у себя. Базовый адрес exe тот-же — $400000, а вот DLL оказалась загруженной по адресу $50000000 (вы можете выяснить это в окне Modules: View/Debug Windows/Modules). Тогда вас интересуют адреса $400000 + $1234 = $401234 и $50000000 + $4321 = $50004321.
Фух, разобрались. Как видите, намного удобнее подключать отладочную информацию, чем работать без неё 🙂
Теперь, что вы собственно должны делать для отладки зависания. Ну, вы запускаете свою программу и работаете с ней, пока она не зависнет. Потом вы запускаете Process Explorer, выбираете в списке процессов свою программу, правый щелчок -> Properties (свойства). В свойствах процесса переходим на вкладку Threads (потоки):

Здесь вы можете посмотреть, чем занимаются потоки вашей программы. Кто кушает процессор, кто чего-то ждёт и т.п. Выберите поток и нажмите кнопку «Stack» для просмотра его стека вызова (пример окна — см. три скриншота чуть выше).
Конечно, вы не сможете посмотреть переменные или что-то такое — только состояние и точки выполнения потоков. Так что вам придётся использовать свои телепатические способности, чтобы определить причину зависания.
Threads Snapshot
Ну, использование Process Explorer хотя и несложно, но не выглядит таковым. Для новичка тут много работы и новых понятий. Да и вся эта возня с отладочной информацией не слишком удобна. Поэтому я написал простую утилитку (сейчас — часть EurekaLog Tools), которая позволяет вам выбрать запущенный процесс и дампит в текстовый файл информацию по всем потокам, включая:
— Базовая инфа: ID, приоритет и т.п.
— Стек вызова. Используются следующие источники отладочной информации: Borland-ские, JCL, EurekaLog и madExcept. Чуть позже добавлю поддержку Microsoft-ских и закачку по запросу, как у Process Explorer.
— Информация от Wait Chain Traversal (на Windows Vista и выше).
— Контекст потока — регистры и флаги.
Собственно, вы запускаете свою проблемную программу и работаете в ней, пока она не зависнет. Потом запускаете утилиту Threads snapshot и тыркаете её на зависший процесс. Она снимет вам снимок потоков, который вы сможете проанализировать.
Интерфейс:

Результат работы:
Process [ 147C / 5244 ]: H:TestProject88.exe (2010.06.08 16:31:07) [ 18C4 / 6340 ] Priority: 8 Wait Chain Count = 1; Deadlock: False Type: Thread; status: Blocked; process: [ 147C / 5244 ]; thread: [ 18C4 / 6340 ]; wait time: 701985; context switches: 4040 EIP: 772E438D EFlags: 00000202 EBP: 0018FF1C ESP: 0018FEEC EAX: 000100B8 EBX: 0008E301 ECX: 00000000 EDX: 00000000 ESI: 00000000 EDI: 0018FEE4 SegCS: 00000023 CegSS: 0000002B SegGS: 0000002B SegFS: 00000053 SegES: 0000002B SegDS: 0000002B ---------------------------------------------------------------------------------------------------------------------------------------------- |Methods |Details|Stack |Address |Module |Unit |Class |Procedure/Method |Line | ---------------------------------------------------------------------------------------------------------------------------------------------- |Calling Thread: ID=6340; Priority=??; Class= | |--------------------------------------------------------------------------------------------------------------------------------------------| |00000008|03 |00000000|772E438D|user32.dll |USER32 | |WaitMessage | | |000000F2|04 |0018FF20|0049F5CC|Project88.exe|Forms |Forms |TApplication.Idle |10358[0] | |000000F2|04 |0018FF20|0049E8FF|Project88.exe|Forms |Forms |TApplication.HandleMessage |9814[23] | |000000F2|04 |0018FF44|0049E8E8|Project88.exe|Forms |Forms |TApplication.HandleMessage |9813[0] | |000000F2|04 |0018FF44|0049EC1D|Project88.exe|Forms |Forms |TApplication.Run |9951[201]| |000000F2|04 |0018FF74|0049EB54|Project88.exe|Forms |Forms |TApplication.Run |9925[0] | |000000F2|04 |0018FF74|004A6C91|Project88.exe|Project88|Project88|Project88 |13[73] | |000000F2|03 |0018FF8C|770D3675|kernel32.dll |kernel32 | |BaseThreadInitThunk | | |000000F2|03 |0018FF98|77B29D70|ntdll.dll |ntdll | |Unknown function at 77B29D70 near RtlInitializeExceptionChain| | |000000F2|03 |0018FFD8|77B29D4B|ntdll.dll |ntdll | |Unknown function at 77B29D4B near RtlInitializeExceptionChain| | |000000F2|03 |0018FFD8|77B29D40|ntdll.dll |ntdll | |Unknown function at 77B29D40 near RtlInitializeExceptionChain| | ---------------------------------------------------------------------------------------------------------------------------------------------- [ 1834 / 6196 ] Priority: 8 Wait Chain Count = 1; Deadlock: False Type: Thread; status: Blocked; process: [ 147C / 5244 ]; thread: [ 1834 / 6196 ]; wait time: 720156; context switches: 2 EIP: 77B100FD EFlags: 00000202 EBP: 02F7FF88 ESP: 02F7FDF4 EAX: 77B51C7F EBX: 77B51C20 ECX: 00000000 EDX: 00000000 ESI: 006B7C60 EDI: 00000000 SegCS: 00000023 CegSS: 0000002B SegGS: 0000002B SegFS: 00000053 SegES: 0000002B SegDS: 0000002B ----------------------------------------------------------------------------------------------------------------------------------- |Methods |Details|Stack |Address |Module |Unit |Class|Procedure/Method |Line| ----------------------------------------------------------------------------------------------------------------------------------- |Calling Thread: ID=6196; Priority=??; Class= | |---------------------------------------------------------------------------------------------------------------------------------| |00000008|03 |00000000|77B100FD|ntdll.dll |ntdll | |ZwWaitForMultipleObjects | | |000000F2|03 |02F7FF8C|770D3675|kernel32.dll|kernel32| |BaseThreadInitThunk | | |000000F2|03 |02F7FF98|77B29D70|ntdll.dll |ntdll | |Unknown function at 77B29D70 near RtlInitializeExceptionChain| | |000000F2|03 |02F7FFD8|77B29D4B|ntdll.dll |ntdll | |Unknown function at 77B29D4B near RtlInitializeExceptionChain| | |000000F2|03 |02F7FFD8|77B29D40|ntdll.dll |ntdll | |Unknown function at 77B29D40 near RtlInitializeExceptionChain| | -----------------------------------------------------------------------------------------------------------------------------------
Как видите — достаточно простая и удобная альтернатива ручной отладке с Process Explorer. Дополнительный плюс — вы можете попросить вашего клиента снять вам снимок процесса на его машине. В случае же с Process Explorer-ом — навряд ли вы сможете объяснить клиенту, что куда ставить и где жать. Не забудьте только передать все необходимые файлы (map-файлы, например).
Я написал эту утилиту буквально только что за два дня. Поэтому она «немножко» сыровата. Нормальная и отлаженная версия этой утилиты должна войти в EurekaLog v7.
Практический пример
Я написал простую программу с двумя кнопками. Вы можете использовать её как обучающий пример. Первая кнопка создаёт несколько потоков, которые ждут друг друга. Вторая кнопка вызывает порчу памяти, приводящую к зависанию. Запустите программу, нажмите на кнопку и попробуйте выяснить причину зависания. Посмотрим, как мы сможем отладить эти два случая.
В Delphi: если у вас есть поддержка WCT, то отладка первой кнопки вообще не представляет сложностей: запустили, нажали, поставили программу на паузу и смотрим окно Threads:

Откуда сразу видно, что у нас есть три потока (не смотрите на последний — это поток отладчика, его можно игнорировать), главный (первый) ждёт завершения рабочего потока (второго), который отправил сообщение (SendMessage) третьему потоку. А третий для обработки сообщения попытался взять критическую секцию, которой владеет второй поток. Т.е. второй поток ждёт третьего потока, а третий ждёт второй поток. Вот вам и взаимная блокировка.
Если же WCT у вас нет, то придётся поработать головой и руками. Вы должны проанализировать стеки вызова каждого потока: щёлкаете по потокам в окне «Threads» и смотрите стеки вызовов:

После переключения на поток открывается окно CPU с текущей выполняемой инструкцией, но вы можете щёлкать по строчкам в окне «Call stack», чтобы посмотреть исходный код.
Проанализировав стеки вызовов всех трёх потоков, вы составите картину произошедшего.
Как эта же ситуация выглядит в Process Explorer и Threads snapshot? Ну, вы запускаете Process Explorer и смотрите стеки потоков (у меня первый поток открывался около минуты, не знаю, с чем связано — потом пошло гладко):

Хотя вы не видите здесь номеров строк, но вы видите имена функций и последовательность вызовов — например, в примере выше обратите внимание на Thread1Func, различные dispatch-функции, CriticalSection.Aquire и т.п. И снова: вы смотрите стеки вызова всех потоков и реконструируете ситуацию. Сделать это будет сложнее, чем используя Delphi (ибо нет номеров строк), но с известной долей телепатии — вполне возможно.
Что касается Threads snapshot, то она даёт такой лог (я отрезал лишние части для уменьшения лога):
Process [ 1A1C / 6684 ]: H:TestHangDemo.exe (2010.06.08 17:54:33) [ 1BD8 / 7128 ] Priority: 8 Wait Chain Count = 7; Deadlock: True Type: Thread; status: Blocked; process: [ 1A1C / 6684 ]; thread: [ 1BD8 / 7128 ]; wait time: 1038023; context switches: 762 Type: Unknown; status: Owned; name: " "; timeout: 0; alertable: 0 Type: Unknown; status: Unknown; name: " "; timeout: 0; alertable: 0 Type: Unknown; status: Unknown; name: ""; timeout: 0; alertable: 0 Type: Unknown; status: Unknown; name: ""; timeout: 0; alertable: 0 Type: Unknown; status: Unknown; name: ""; timeout: 0; alertable: 0 Type: Unknown; status: Unknown; name: ""; timeout: 0; alertable: 0 EIP: 77B0F871 EFlags: 00000202 EBP: 0018F438 ESP: 0018F3CC EAX: 00000000 EBX: 00000000 ECX: 00000000 EDX: 00000000 ESI: 00000120 EDI: 00000000 SegCS: 00000023 CegSS: 0000002B SegGS: 0000002B SegFS: 00000053 SegES: 0000002B SegDS: 0000002B ---------------------------------------------------------------------------------------------------------------------------------------------- |Methods |Details|Stack |Address |Module |Unit |Class |Procedure/Method |Line | ---------------------------------------------------------------------------------------------------------------------------------------------- |Calling Thread: ID=7128; Priority=??; Class= | |--------------------------------------------------------------------------------------------------------------------------------------------| |00000008|03 |00000000|77B0F871|ntdll.dll |ntdll | |ZwWaitForSingleObject | | |000000F2|03 |0018F43C|7757077E|KERNELBASE.dll|KERNELBASE| |WaitForSingleObjectEx | | |000000F2|03 |0018F43C|770D117F|kernel32.dll |kernel32 | |WaitForSingleObjectEx | | |000000F2|03 |0018F454|770D1141|kernel32.dll |kernel32 | |WaitForSingleObjectEx | | |000000F2|03 |0018F454|770D1133|kernel32.dll |kernel32 | |WaitForSingleObject | | |000000F2|03 |0018F468|770D1126|kernel32.dll |kernel32 | |WaitForSingleObject | | |000000F2|04 |0018F468|004B3580|HangDemo.exe |UnitMain |UnitMain|TfmMain.Button1Click |101[68] | |000000F2|04 |0018F47C|0048331F|HangDemo.exe |Controls |Controls|TControl.Click |7178[111] | ---------------------------------------------------------------------------------------------------------------------------------------------- [ 065C / 1628 ] Priority: 8 Wait Chain Count = 5; Deadlock: True Type: Thread; status: Blocked; process: [ 1A1C / 6684 ]; thread: [ 065C / 1628 ]; wait time: 1038024; context switches: 34 Type: Unknown; status: No access; name: " "; timeout: 0; alertable: 0 Type: Unknown; status: Unknown; name: " "; timeout: 0; alertable: 0 Type: Unknown; status: Unknown; name: ""; timeout: 0; alertable: 0 Type: Unknown; status: Unknown; name: ""; timeout: 0; alertable: 0 EIP: 77B0F871 EFlags: 00000202 EBP: 0237FD40 ESP: 0237FCDC EAX: 00000000 EBX: 00000000 ECX: 00000000 EDX: 00000000 ESI: 006293C4 EDI: 00000000 SegCS: 00000023 CegSS: 0000002B SegGS: 0000002B SegFS: 00000053 SegES: 0000002B SegDS: 0000002B ------------------------------------------------------------------------------------------------------------------------------------------- |Methods |Details|Stack |Address |Module |Unit |Class |Procedure/Method |Line | ------------------------------------------------------------------------------------------------------------------------------------------- |Calling Thread: ID=1628; Priority=??; Class= | |-----------------------------------------------------------------------------------------------------------------------------------------| |00000008|03 |00000000|77B0F871|ntdll.dll |ntdll | |ZwWaitForSingleObject | | |000000F2|03 |0237FD44|77B28B9A|ntdll.dll |ntdll | |Unknown function at 77B28B9A near RtlIntegerToUnicodeString | | |000000F2|03 |0237FD44|77B28B43|ntdll.dll |ntdll | |Unknown function at 77B28B43 near RtlIntegerToUnicodeString | | |000000F2|03 |0237FD6C|77B12260|ntdll.dll |ntdll | |RtlEnterCriticalSection | | |000000F2|04 |0237FD6C|00441408|HangDemo.exe|SyncObjs|SyncObjs|TCriticalSection.Acquire |546[4] | |000000F2|04 |0237FD74|004B3354|HangDemo.exe|UnitMain|UnitMain|WndProc |43[20] | |000000F2|03 |0237FDA8|772D6215|user32.dll |USER32 | |Unknown function at 772D6215 near gapfnScSendMessage | | |000000F2|03 |0237FDA8|772D68E5|user32.dll |USER32 | |Unknown function at 772D68E5 near gapfnScSendMessage | | |000000F2|03 |0237FDEC|772D6893|user32.dll |USER32 | |Unknown function at 772D6893 near gapfnScSendMessage | | |000000F2|03 |0237FE20|772D682D|user32.dll |USER32 | |Unknown function at 772D682D near gapfnScSendMessage | | |000000F2|03 |0237FE20|772D7172|user32.dll |USER32 | |Unknown function at 772D7172 near GetWindowLongW | | |000000F2|03 |0237FEA8|772D682D|user32.dll |USER32 | |Unknown function at 772D682D near gapfnScSendMessage | | |000000F2|03 |0237FEA8|772D7D2C|user32.dll |USER32 | |Unknown function at 772D7D2C near LoadStringW | | |000000F2|03 |0237FEF0|772D7E2C|user32.dll |USER32 | |DispatchMessageW | | |000000F2|03 |0237FEF0|772D7EB8|user32.dll |USER32 | |GetMessageW | | |000000F2|03 |0237FF0C|772D7E92|user32.dll |USER32 | |GetMessageW | | |000000F2|04 |0237FF0C|004B3420|HangDemo.exe|UnitMain|UnitMain|Thread1Func |71[132] | |000000F2|04 |0237FF78|00405FE4|HangDemo.exe|System |System |ThreadWrapper |13579[40]| |000000F2|03 |0237FF8C|770D3675|kernel32.dll|kernel32| |BaseThreadInitThunk | | |000000F2|03 |0237FF98|77B29D70|ntdll.dll |ntdll | |Unknown function at 77B29D70 near RtlInitializeExceptionChain| | |000000F2|03 |0237FFD8|77B29D4B|ntdll.dll |ntdll | |Unknown function at 77B29D4B near RtlInitializeExceptionChain| | |000000F2|03 |0237FFD8|77B29D40|ntdll.dll |ntdll | |Unknown function at 77B29D40 near RtlInitializeExceptionChain| | ------------------------------------------------------------------------------------------------------------------------------------------- [ 1504 / 5380 ] Priority: 8 Wait Chain Count = 5; Deadlock: True Type: Thread; status: Blocked; process: [ 1A1C / 6684 ]; thread: [ 1504 / 5380 ]; wait time: 1038024; context switches: 8 Type: Unknown; status: Running; name: " "; timeout: 0; alertable: 0 Type: Unknown; status: Unknown; name: " "; timeout: 0; alertable: 0 Type: Unknown; status: Unknown; name: ""; timeout: 0; alertable: 0 Type: Unknown; status: Unknown; name: ""; timeout: 0; alertable: 0 EIP: 772D723B EFlags: 00000202 EBP: 03C1FF28 ESP: 03C1FEE8 EAX: 00BD2C30 EBX: 005E0EC0 ECX: 00000000 EDX: 00000000 ESI: 00BD2C30 EDI: 00000401 SegCS: 00000023 CegSS: 0000002B SegGS: 0000002B SegFS: 00000053 SegES: 0000002B SegDS: 0000002B ------------------------------------------------------------------------------------------------------------------------------------------- |Methods |Details|Stack |Address |Module |Unit |Class |Procedure/Method |Line | ------------------------------------------------------------------------------------------------------------------------------------------- |Calling Thread: ID=5380; Priority=??; Class= | |-----------------------------------------------------------------------------------------------------------------------------------------| |00000008|03 |00000000|772D723B|user32.dll |USER32 | |Unknown function at 772D723B near GetPropW | | |000000F2|03 |03C1FF2C|772DCC02|user32.dll |USER32 | |Unknown function at 772DCC02 near GetWindow | | |000000F2|03 |03C1FF2C|772DCD7C|user32.dll |USER32 | |SendMessageW | | |000000F2|03 |03C1FF50|772DCD35|user32.dll |USER32 | |SendMessageW | | |000000F2|04 |03C1FF50|004B350F|HangDemo.exe|UnitMain|UnitMain|Thread2Func |85[67] | |000000F2|04 |03C1FF78|00405FE4|HangDemo.exe|System |System |ThreadWrapper |13579[40]| |000000F2|03 |03C1FF8C|770D3675|kernel32.dll|kernel32| |BaseThreadInitThunk | | |000000F2|03 |03C1FF98|77B29D70|ntdll.dll |ntdll | |Unknown function at 77B29D70 near RtlInitializeExceptionChain| | |000000F2|03 |03C1FFD8|77B29D4B|ntdll.dll |ntdll | |Unknown function at 77B29D4B near RtlInitializeExceptionChain| | |000000F2|03 |03C1FFD8|77B29D40|ntdll.dll |ntdll | |Unknown function at 77B29D40 near RtlInitializeExceptionChain| | -------------------------------------------------------------------------------------------------------------------------------------------
К сожалению, вывод WCT пока не очень форматирован (это моё домашнее задание! 🙂 ), но цепочку ожиданий увидеть можно. Плюс стеки потоков (к сожалению, без системной отладочной информации — это моё второе домашнее задание!) самым решительным образом намекают на происходящее.
Ничего сложного.
Второй случай сложнее. Потому что зависание -лишь внешнее проявление другой проблемы.
Итак, в Delphi вы запускаете программу, она виснет, мы ставим её на паузу. У нас только один поток, поэтому WCT нам не помощник, даже если он есть. Поэтому сразу переключаемся на главный поток и смотрим стек вызовов:

Опять-таки: щёлкая по стеку вызовов, мы видим исходный код.
В этот раз, простой анализ стека вызовов нам не помогает. Пока что неясно, что же случилось. Тем более, что программа вроде бы не висит, а что-то делает (т.е., строго говоря, у нас не зависание, а зацикливание). Поэтому мы начинаем пошаговый прогон программы. Пройдясь по коду мы видим, что код постоянно крутит цикл со Sleep, проверяя некий флаг. Мы видим, что этот код — код менеджера памяти FastMM. Почитав комментарии в коде, мы узнаём, что FastMM пытается получить блокировку. А проверяемый флаг — это признак занят/свободен. Поскольку FastMM проверяет этот флаг уже полчаса — ясно, что тут что-то не так. Этот же вывод следует из того, что в нашёй программе всего один поток — т.е. ждать-то и вовсе некого, кроме нас в программе никого нет. Иными словами, состояние флага не соответствует действительности — т.е. он испорчен вследствие повреждения памяти.
К сожалению, найти повреждение памяти не так-то просто и это отдельная тема, которую я недавно закончил обсуждать.
Поэтому, задача анализа зависания — выяснить причину. Мы её нашли: это — повреждение памяти. Дальше, анализ зависания закончен, но проблема пока не найдена и не устранена — мы переходим к анализу и поиску проблем с памятью.
Собственно, второй случай выглядит примерно аналогично везде (в Delphi, Process Exporer и Threads snapshot): мы получаем примерно один и тот же стек вызовов, который может меняться время от времени, но всегда это будет цикл и с вероятностью в 99% — стоять на Sleep. Делается просто несколько проверок, чтобы в этом убедиться. Для примера — вот как это выглядит в Process Explorer:

Напомню, что из этого мы вынесем только вывод (не однозначный, конечно же, а всего лишь обоснованное предположение), что у нас есть повреждение памяти. Дальше — это уже другая история.
Фух. На сегодня у меня всё. Надеюсь, этот материал был полезен. Если хотите, вот ещё дополнение — презентация по ручному поиску места ошибки по адресу (см. «How to find the exception source line»). На английском, но может пригодится или будет интересно.
Читать далее: Как узнать почему программа внезапно закрывается?
First create all the threads, then join all of them:
pthread_t tid[2];
/// create all threads
for (int i = 0; i < 2; i++) {
pthread_create(&tid[i], NULL, routine, NULL);
}
/// wait all threads by joining them
for (int i = 0; i < 2; i++) {
pthread_join(tid[i], NULL);
}
Alternatively, have some pthread_attr_t variable, use pthread_attr_init(3) then pthread_attr_setdetachedstate(3)
on it, then pass its address to pthread_create(3) second argument. Thos would create the threads in detached state. Or use pthread_detach as explained in Jxh’s answer.
Remember to read some good Pthread tutorial. You may want to use mutexes and condition variables.
You could use frameworks wrapping them, e.g. Qt or POCO (in C++), or read a good C++ book and use C++ threads.
Conceptually, threads have each their call stack and are related to continuations. They are «heavy».
Consider some agent-oriented programming approach: as a rule of thumb, you don’t want to have a lot of threads (e.g. 20 threads on a 10 core processor is reasonable, 200 threads won’t be unless a lot of them are sleeping or waiting) and and do want threads to synchronize using mutex and condition variables and communicate and/or synchronize with other threads quite often (several times per second). See also poll(2), fifo(7), unix(7), sem_overview(7) with shm_overview(7) as another way of communicating between threads. In general, avoid using signal(7) with threads (read signal-safety(7)…), and use dlopen(3) with caution (probably only in the main thread).
A pragmatical approach would be to have most of your threads running some event loop (using poll(2), pselect(2), perhaps eventfd(2), signalfd(2), ….), perhaps communicating using pipe(7) or unix(7) sockets. See also socket(7).
Don’t forget to document (on paper) the communication protocols between threads. For a theoretical approach, read books about π-calculus and be aware of Rice’s theorem : debugging concurrent programs is difficult.