6 ответов

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

Изменить: Хорошо, так что это игра, и вы проводите опрос, чтобы посмотреть, загружен ли файл как часть цикла рендеринга. Вот что я хотел бы попробовать: используйте ReadFileEx для запуска перекрытого чтения. Это не будет заблокировано. Затем в вашем основном цикле вы можете проверить, выполняется ли чтение, используя одну из функций Wait с нулевым таймаутом. Это также не будет блокировать.

если вы можете рассмотреть варианты с открытым исходным кодом, у Java есть блокирующая очередь [ссылка ], как и Python [ссылка . Это уменьшит ваш код до (очередь здесь привязана к load_fcn, т.е. С использованием закрытия)

Def load_fcn(): while True: queue.get().loadFileAndMemcpy() threading.Thread(target=load_fcn).start()

Даже если вы, возможно, не должны их использовать, потоки python 3.0 имеют функцию _stop(), а потоки python2.0 имеют функцию _Thread__stop. Вы также можете записать значение "Нет" в очередь и проверить load_fcn().

Кроме того, поиск stackoverflow для " gui" и "[субъективный] ", если хотите.

Основываясь на информации, присутствующей в этой точке, я предполагаю, что что-то в обработчике загрузки файла взаимодействует с вашим основным циклом. Я не знаю библиотеки, но на основании вашего описания обработчик файлов делает что-то в следующих строках:

  • Загрузка необработанных двоичных данных для файла 20 тыс.
  • Интерпретировать 20k как PNG файл
  • Загрузка в структуру, представляющую изображение с разрешением 2048 × 2048 пикселей

Следующие возможности напоминают библиотеки, которые вы используете для достижения следующих шагов:

  • Может ли быть, что распределение памяти для несжатых данных изображения удерживает блокировку, которая требуется основному потоку для любых выполняемых ею графических/интерактивных операций?
  • Может ли быть, что вызов, который отвечает за перевод PNG-данных в пиксели, фактически содержит блокировку библиотеки нижнего уровня, которая неблагоприятно взаимодействует с вашим основным потоком?

Лучший способ получить дополнительную информацию - попытаться смоделировать активность обработчика загрузчика файлов без использования текущего кода в нем... написать процедуру самостоятельно, которая выделяет правильный размер блока памяти и выполняет некоторую обработку который превращает 20k исходных данных в структуру размером целевого блока... затем добавляет к нему еще одну логику за раз, пока вы не сузились, когда производительность падает, чтобы изолировать виновника.

Я бы написал этот цикл таким образом, за исключением блокировки разблокировки, которая могла бы быть испорчена: P:

Void fileLoadThreadFunc(void *arglist) { while(true) { loadObj *obj = NULL; // protect all access to the vector s_mutex.lock(); if(s_filesToLoad.size() != 0) { obj = s_filesToLoad; s_filesToLoad.erase(s_filesToLoad.begin()); } s_mutex.unlock(); if(obj != NULL) obj->loadFileAndMemcpy(); else Sleep(10); } }

Не уверен в вашей конкретной проблеме, но вы действительно должны мьютекс-защищать вызов по размеру.

Void fileLoadThreadFunc(void *arglist) { while (true) { s_mutex.lock(); while (s_filesToLoad.size() == 0) { s_mutex.unlock(); Sleep(10); s_mutex.lock(); } loadObj *obj = s_filesToLoad; s_filesToLoad.erase(s_filesToLoad.begin()); s_mutex.unlock(); obj->loadFileAndMemcpy(); } }

Теперь, исследуя вашу конкретную проблему, я не вижу ничего плохого в коде, который вы предоставили. Основной поток и поток загрузчика файлов должны с радостью работать бок о бок, если этот мьютекс является единственным конкурентом между ними.

Я говорю, что, поскольку в стандартной библиотеке могут присутствовать другие точки состязания, которые не отображаются вашим примером кода.

22/02/2018

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

03/07/2017

EagleGet - простой менеджер закачек, позволяющий скачивать любые файлы из интернета на высокой скорости, а также обладающий понятным интерфейсом, с которым может разобраться практически любой пользователь. Отличительной особенностью данной программы является то, что она автоматически настраивает параметры загрузки, исходя из возможностей и особенностей вашего Интернет-соединения. Кроме того позволяет вам не беспокоиться о том, что закачка будет прервана из-за неполадок. Программа сможет продолжить загрузку файла с того места, где она была прервана. Если, конечно, данная возможность поддерживается сайтом, с которого вы загружает...

27/06/2017

Download Master - один из популярнейших менеджеров закачек. Он отличается от других таких программ высокой продуктивностью и доступным для пользователя интерфейсом. Как считается, существует три проблемы при скачивании файлов из сети: скорость скачивания, продолжение прерванных закачек и управление уже скачанными файлами. Download Master решает их все. С помощью HTTP, HTTPS и FTP протоколов эта программа значительно повышает скорость скачивания из Интернета. Это достигается при помощи технологии разбивания файла на потоки информации, которые скачиваются в одно время. Если интернет соединение во время скачивания прерва...

12/05/2017

29/11/2016

Free Download Manager – бесплатный менеджер закачек, обладающий широкими возможностями. Данная программа может скачивать файлы на максимальной скорости, поскольку увеличивает её за счёт использования нескольких потоков загрузки, однако это далеко не все её функции. Прежде всего, следует отметить тот факт, что Free Download Manager является ещё и Torrent клиентов. Т.е. по сути это две программы в одной. Кроме того, данная программа обладает функцией скачивания сайтов целиком. Это бывает полезно, когда у вас не очень хороший интернет, да и оплата производится за время соединения, то иногда выгоднее скачать весь сайт сра...

27/11/2015

JDownloader - полезное приложение, предназначенное для быстрого скачивания медиа файлов, документов либо программ. Позволяет закачивать файлы в разы быстрее, чем с помощью загрузчик браузера. Все закачки можно ставить в свою очередь. Настройки программы позволяют запускать скачивание сразу после добавления ссылки. Работает на базе распространённой платформы Java. Отлично взаимодействует с популярными файлообменными хранилищами. Приложение вынимает ссылку на файл из сети и справляется с ограничителями скорости. Позволяет беспрепятственно скачивать информацию с файлообменников. Интерфейс менеджера закачек включает три закладки.

07/07/2015

Ninja Download Manager - весьма удобный менеджер закачек, с которым можно без особых трудностей загружать большие файлы. Программа позволяет останавливать или возобновлять загрузку файла, а также увеличивает скорость до максимально возможной. Приложение может автоматически перехватывать все ссылки, находящиеся в буфере обмена операционной среды. Ninja Download Manager позволяет перетаскивать файлы посредством drag&drop. Можно упорядочиваться загрузки по желанию пользователя. Возможность составления расписания помогает начинать либо останавливать загрузки без участия пользователя. Помимо этих функций Ninja Download Manager спосо...

16/03/2015

SD Download Manager – приложение для загрузки, скачивания различных файлов, документов из интернет-источников. Работает быстрее браузерного загрузчика, благодаря использованию до 32 активных подключений. Новый динамический алгоритм сжатия и скачивания данных делает загрузку документов и различных файлов быстрее в разы. Утилита помогает возобновить, прерванные по причинам, операции скачивания, а также взаимодействует с битыми ссылками. Поддерживает протоколы интернет-соединений HTTP, HTTPS, FTP-протоколы, брандмауэры, файл перенаправления авторизации. Использует захват загрузок с веб-браузера Mozilla Firefox, также проводит мон...

10/03/2015

DAM - создана для скачивания файлов с интернета. Может не только скачать отдельные файлы, а также скачивает музыку и видео с таких сайтов как например YouTube. Главная особенность программы в том что она многократно увеличивает скорость загрузки, по сравнению с базовым загрузчиком который присутствует в других браузерах. Поддерживается всеми популярными веб-браузерами, Firefox, IE, Chrome и т.д. Программа может планировать, восстанавливать и запускать остановленные загрузки. В отличие от других загрузчиков, не тратит время зря на бесполезные действия и сразу сохраняет файлы полностью, а не по частям. Не имеет какого либо шпионс...

18/11/2014

Большинство людей каждый день пользуются Интернетом. И очень часто приходится скачивать книги, фильмы, музыку, программы и так далее. Пользоваться обычными программами для загрузки, а тем более стандартными средствами - долго и неудобно. И именно для того, чтобы максимально повысить уровень работы в Интернете, были разработаны так называемые Менеджеры загрузок, то есть программы, предоставляющие необходимые средства. Одна из них (и наиболее популярная) - Download Accelerator Plus. Так в чем же заключается принцип работы программы? Скачиваемый файл буквально "разрезается" на части, и все они загружаются одновременно.

10/11/2014

DDownloads – это приложение, которое содержит в себе специальный каталог, выполняющий функции оперативного поиска, а также установки различных необходимых программ. Данный каталог представлен в виде определенного списка ссылок на разные бесплатные программы. Оснащен встроенным модулем, выполняющим автоматические функции по обновлению, что позволяет регулярно расширять и актуализировать ссылки. Каталог разделен на несколько категорий для удобного поиска. Для каждой из программ в DDownloads содержится описание и характеристики, информация по их рейтингу и о производителях. Размещенные ссылки позволяют скачать разные версии програ...

29/09/2013

Orbit Downloader – ещё один менеджер закачек с удобным интерфейсом и некоторыми уникальными возможностями. Программа способна скачивать файлы по всем стандартным протоколам, включая http, https, ftp, а также обладает функцией остановки и старта любой из закачек, если, конечно, данный способ поддерживается сервером. Программа имеет встроенный планировщик, позволяющий выполнять все закачки в указанное вами время, например, вы можете запланировать старт всех закачек на 2 часа ночи. Помимо этого в стандартные функции программы входит возможность многопоточной закачки, а также интеграция во все популярные браузеры.

17/06/2013

Мощная качалка файлов, заметно ускоряющая время загрузки файлов благодаря разбиению файла на несколько потоков. Программа умеет отсортировать файлы по типу, перехватить ссылки из буфера обмена, интернироваться в браузеры Google Chrome и Mozilla Firefox и т.д. Кроме этого в программе присутствует детектор ресурсов (встроенный компактный браузер), с помощью которого можно легко "перехватить" все имеющиеся файлы на указанной вами страницы. Естественно, предусмотрены импорт и экспорт списков заданий, а также поддержка прокси и даже создание и скачивание торрент файлов. Таким образом можно сказать, что FlashGet это универсальный мен...

1.1. Определение потока

Потоком в Windows называется объект ядра, которому операционная система выделяет процессорное время для выполнения приложения. Каждому потоку принадлежат следующие ресурсы:

  • код исполняемой функции;
  • набор регистров процессора;
  • стек для работы приложения;
  • стек для работы операционной системы;
  • маркер доступа, который содержит информацию для системы безопасности.

Все эти ресурсы образуют контекст потока в Windows . Кроме дескриптора каждый поток в Windows также имеет свой идентификатор, который уникален для потоков выполняющихся в системе. Идентификаторы потоков используются служебными программами, которые позволяют пользователям системы отслеживать работу потоков.

В операционных системах Windows различаются потоки двух типов:

  • системные потоки;
  • пользовательские потоки.

Системные потоки выполняют различные сервисы операционной системы и запускаются ядром операционной системы.

Пользовательские потоки служат для решения задач пользователя и запускаются приложением.

В работающем приложении различаются потоки двух типов:

  • рабочие потоки (working threads);
  • потоки интерфейса пользователя (user interface threads).

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

Создается поток функцией CreateThread

function CreateThread (
lpThreadAttributes: Pointer; // атрибуты защиты
dwStackSize: DWORD; // размер стека потока в байтах
lpStartAddress: TFNThreadStartRoutine; // адрес функции
lpParameter: Pointer; // адрес параметра
dwCreationFlags: DWORD; // флаги создания потока
var lpThreadId: DWORD // идентификатор потока
): THandle;

При успешном завершении функция CreateThread возвращает дескриптор созданного потока и его идентификатор, который является уникальным для всей системы. В противном случае эта функция возвращает значение nil .

Назначение параметров

lpThreadAttributes

Параметр lpThreadAttributes устанавливает атрибуты защиты создаваемого потока. До тех пор пока мы не изучим систему безопасности в Windows, мы будем устанавливать значения этого параметра в nil при вызове почти всех функций ядра Windows. В данном случае это означает, что операционная система сама установит атрибуты защиты потока, используя настройки по умолчанию.

Параметр dwStacksize определяет размер стека, который выделяется потоку при запуске. Если этот параметр равен нулю, то потоку выделяется стек, размер которого по умолчанию равен 1 Мбайт . Это наименьший размер стека, который может быть выделен потоку. Если величина параметра dwStacksize меньше значения, заданного по умолчанию, то все равно потоку выделяется стек размером в 1 Мбайт . Операционная система Windows округляет размер стека до одной страницы памяти, который обычно равен 4 Кбайт .

Параметр lpStartAddress указывает на исполняемую потоком функцию.

Видно, что функции потока может быть передан единственный параметр lpParameter , который является указателем на пустой тип. Это ограничение следует из того, что функция потока вызывается операционной системой, а не прикладной программой. Программы операционной системы являются исполняемыми модулями и поэтому они должны вызывать только функции, сигнатура которых заранее определена. Поэтому для потоков определили самый простой список параметров, который содержит только указатель. Так как функции потоков вызываются операционной системой, то они также получили название функции обратного вызова .

Параметр dwCreationFiags определяет, в каком состоянии будет создан поток. Если значение этого параметра равно 0, то функция потока начинает выполняться сразу после создания потока. Если же значение этого параметра равно CREATE_SUSPENDED , то поток создается в подвешенном состоянии. В дальнейшем этот поток можно запустить вызовом функции ResumeThread .

Параметр lpThreadId является выходным, т. е. его значение устанавливает Windows. Этот параметр должен указывать на переменную, в которую Windows поместит идентификатор потока. Этот идентификатор уникален для всей системы и может в дальнейшем использоваться для ссылок на поток. Идентификатор потока главным образом используется системными функциями и редко функциями приложения. Действителен идентификатор потока только на время существования потока. После завершения потока тот же идентификатор может быть присвоен другому потоку.

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

В листинге 1.1 приведен пример программы, которая использует функцию CreateThread для создания потока и демонстрирует способ передачи параметров исполняемой потоком функции.

Листинг 1.1. Создание потока функцией CreateThread

Program CreateThreadd; {$APPTYPE CONSOLE} uses SysUtils, Windows; var n: Integer = 0; inc: Integer = 10; hThread: HWND; IDThread: DWORD; procedure Add(iNum: Pointer); stdcall; begin Writeln("Thread is started"); n:= n + Integer(iNum^); Writeln("Thread is finished"); end; begin Writeln("n = ", n); //запускаем поток Add hThread:= CreateThread(nil, 0, @Add, @inc, 0, IDThread); //Ждем, пока поток Add закончит работу WaitForSingleObject(hThread, INFINITE); //закрываем дескриптор потока Add CloseHandle(hThread); Writeln("n = ", n); Readln; end.

Отметим, что в этой программе используется функция WaitForSingleObject , которая ждет завершения потока Add .

Поток завершается вызовом функции ExitThread , которая имеет следующий прототип:

procedure ExitThread (
dwExitCode: DWORD //код завершения потока
); stdcall;

Эта функция может вызываться как явно, так и неявно при возврате значения из функции потока. При выполнении этой функции система посылает динамическим библиотекам, которые загружены процессом, сообщение DLL_THREAD_DETACH , которое говорит о том, что поток завершает свою работу.

Один поток может завершить другой поток, вызвав функцию TerminateThread

function TerminateThread (
hThread: THandle; //дескриптор потока
dwExitCode: DWORD; //код завершения потока
): BOOL; stdcall;

В случае успешного завершения функция TerminateThread возвращает ненулевое значение, в противном случае - FALSE . Функция TerminateThread завершает поток, но не освобождает все ресурсы, принадлежащие этому потоку. Это происходит потому, что при выполнении этой функции система не посылает динамическим библиотекам, загруженным процессом, сообщение о том, что поток завершает свою работу. В результате динамическая библиотека не освобождает ресурсы, которые были захвачены для работы с этим потоком. Поэтому эта функция должна вызываться только в аварийных ситуациях при зависании потока.

В листинге 1.2 приведена программа, которая демонстрирует работу функции TerminateThread .

Program TerminateThreadd; {$APPTYPE CONSOLE} uses SysUtils, Windows; var count: Cardinal = 0; hThread: HWND; IDThread: DWORD; c: Char; b1: Boolean = True; procedure thread; stdcall; var b2: Boolean; begin b2:= True; while b2 do begin count:= count + 1; Sleep(100); // немного отдохнем end; end; begin hThread:= CreateThread(nil, 0, @thread, nil, 0, IDThread); while b1 do begin Write("Input ""y"" to display the count or any char to finish: "); Readln(c); if c = "y" then Writeln("count = ", count) else Break; end; //прерываем выполнение потока thread TerminateThread(hThread, 0); //закрываем дескриптор потока CloseHandle(hThread); end.

Каждый созданный поток имеет счетчик приостановок, максимальное значение которого равно MAXIMUM_SUSPEND_COUNT . Счетчик приостановок показывает, сколько раз исполнение потока было приостановлено. Поток может исполняться только при условии, что значение счетчика приостановок равно нулю. В противном случае поток не исполняется или, как говорят, находится в подвешенном состоянии. Исполнение каждого потока может быть приостановлено вызовом функции SuspendThread , которая имеет следующий прототип:

function SuspendThread (
hThread: THandle //дескриптор потока
): DWORD; stdcall;

Эта функция увеличивает значение счетчика приостановок на 1 и, при успешном завершении, возвращает текущее значение этого счетчика. В случае неудачи функция SuspendThread возвращает значение, равное -1.

Отметим, что поток может приостановить также и сам себя. Для этого он должен передать функции SuspendThread свой псевдодескриптор, который можно получить при помощи функции GetCurrentThread .

Для возобновления исполнения потока используется функция ResumeThread , которая имеет следующий прототип:

function ResumeThread (
hThread: THandle //дескриптор потока
): DWORD; stdcall;

Функция ResumeThread уменьшает значение счетчика приостановок на 1 при условии, что это значение было больше нуля. Если полученное значение счетчика приостановок равно 0, то исполнение потока возобновляется, в противном случае поток остается в подвешенном состоянии. Если при вызове функции ResumeThread значение счетчика приостановок было равным 0, то это значит, что поток не находится в подвешенном состоянии. В этом случае функция не выполняет никаких действий. При успешном завершении функция ResumeThread возвращает текущее значение счетчика приостановок, в противном случае - значение -1.

Поток может задержать свое исполнение вызовом функции Sleep , которая имеет следующий прототип:

procedure Sleep (
dwMilliseconds: DWORD //миллисекунды
); stdcall;

Единственный параметр функции Sleep определяет количество миллисекунд, на которые поток, вызвавший эту функцию, приостанавливает свое исполнение. Если значение этого параметра равно 0, то выполнение потока просто прерывается, а затем возобновляется при условии, что нет других потоков, ждущих выделения процессорного времени. Если же значение этого параметра равно INFINITE , тo поток приостанавливает свое исполнение навсегда, что приводит к блокированию работы приложения.

В листинге 1.3 приведена программа, которая демонстрирует работу функций SuspendThread , ResumeThread и Sleep .

//Пример работы функций SuspendThread, ResumeThread и Sleep program SuspendThreadd; {$APPTYPE CONSOLE} uses SysUtils, Windows; var nCount: Cardinal = 0; dwCount: DWORD; hThread: HWND; IDThread: DWORD; c: Char; b: Boolean = True; procedure thread; stdcall; begin while b do begin nCount:= nCount + 1; Sleep(100); // приостанавливаем поток на 100 миллисекунд end; end; begin hThread:= CreateThread(nil, 0, @thread, nil, 0, IDThread); while b do begin Writeln("Input:"); Writeln(#9, """n"" to exit"); Writeln(#9, """y"" to display the count"); Writeln(#9, """s"" to suspend thread"); Writeln(#9, """r"" to resume thread"); Readln(c); case c of "n": Break; "y": Writeln("count = ", nCount); "s": begin //приостанавливаем поток thread dwCount:= SuspendThread(hThread); Writeln("Thread suspend count = ", dwCount); end; "r": begin //возобнавляем поток thread dwCount:= ResumeThread(hThread); Writeln("Thread suspend count = ", dwCount); end; end; end; //прерываем выполнение потока thread TerminateThread(hThread, 0); //закрываем дескриптор потока CloseHandle(hThread); end.

Иногда потоку требуется знать свой дескриптор, чтобы изменить какие-то свои характеристики. Например, поток может изменить свой приоритет. Для этих целей в Win32 API существует функция GetcurrentThread , которая имеет следующий прототип:

function GetCurrentThread : THandle; stdcall;

и возвращает псевдодескриптор текущего потока. Псевдодескриптор текущего потока отличается от настоящего дескриптора потока тем, что он может использоваться только самим текущим потоком и, следовательно, может наследоваться другими процессами. Псевдодескриптор потока не нужно закрывать после его использования. Из псевдодескриптора потока можно получить настоящий дескриптор потока, для этого псевдодескриптор нужно продублировать, вызвав функцию DuplicateHandle .

В листинге 1.4 приведен пример программы, которая вызывает функцию GetCurrentThread , а затем выводит на консоль полученный псевдодескриптор.

//Пример работы функции GetcurrentThread program GetCurrentThreadd; {$APPTYPE CONSOLE} uses SysUtils, Windows; var hThread: HWND; begin // получаем псевдодескриптор текущего потока hThread:= GetCurrentThread; // получаем псевдодескриптор текущего потока Writeln(hThread); Readln; end.

Большинство функций Win32 API возвращают код, по которому можно определить, как завершилась функция: успешно или нет. Если функция завершилась неудачей, то код возврата обычно равен false , nil или -1. В этом случае функция Win32 API также устанавливает внутренний код ошибки, который называется кодом последней ошибки (last-error code) и поддерживается отдельно для каждого потока. Чтобы получить код последней ошибки, нужно вызвать функцию GetLastError , которая имеет следующий прототип:

function GetLastError : DWORD; stdcall;

Эта функция возвращает код последней ошибки, установленной в потоке. Установить код последней ошибки в потоке можно при помощи функции SetLastError , имеющей следующий прототип:

procedure SetLastError (
dwErrCode: DWORD //код ошибки
); stdcall;

Чтобы получить сообщение, соответствующее коду последней ошибки, необходимо использовать функцию FormatMessage , которая имеет следующий прототип:

function FormatMessage (
dwFlags: DWORD; // режимы форматирования
lpSource: Pointer; // источник сообщения
dwMessageId: DWORD; // идентификатор сообщения
dwLanguageId: DWORD; // идентификатор языка
lpBuffer: PChar; // буфер для сообщения
nSize: DWORD; // максимальный размер буфера для сообщения
Arguments: Pointer // список значений для вставки в сообщение
): DWORD; stdcall;

В листинге 1.5 приведен пример программы, которая вызывает функцию FormatMessage

Program ErrorMessageBoxx; {$APPTYPE CONSOLE} uses SysUtils, Windows; var hHandle: THandle; procedure ErrorMessageBox; var lpMsgBuf: PChar; begin FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER or FORMAT_MESSAGE_FROM_SYSTEM or FORMAT_MESSAGE_IGNORE_INSERTS, nil, GetLastError, 0, @lpMsgBuf, 0, nil); MessageBox(0, lpMsgBuf, "Error Win32 API", MB_OK or MB_ICONINFORMATION); //Освободить буфер LocalFree(Integer(lpMsgBuf)); end; //тест для функции вывода сообщения об ошибке на консоль begin hHandle:= 0; //неправильный вызов функции закрытия дескритптора if not CloseHandle(hHandle) then ErrorMessageBox; end.

Исходный код скачать . Выполнен на Delphi XE.

Используемая литература: Александр Побегайло "Системное программироввние в Windows"

Это четвертая статья из серии "Преодолевая границы Windows", в рамках которой я рассказываю об ограничениях, существующих для фундаментальных ресурсов в Windows. На сей раз, я собираюсь обсудить с вами ограничение на максимальное количество потоков и процессов, поддерживаемое Windows. Здесь я кратко опишу различие между потоком и процессом, ограничение потока опроса (от англ. survey thread), после чего мы поговорим об ограничениях, связанных с процессами. В первую очередь я решил рассказать об ограничениях потоков, так как каждый активный процесс имеет, по крайней мере, один поток (процесс, который завершился, но ссылка на который хранится в обработчике, предоставленном другим процессом, не имеет ни одного потока), так что ограничения процессов напрямую зависят от основных ограничений, связанных с потоками.

В отличие от некоторых вариантов UNIX, большинство ресурсов Windows не имеют фиксированного ограничения, заложенного в операционную систему на этапе сборки, а скорее получают ограничения на основании имеющихся в распоряжении ОС базовых ресурсов, о которых я рассказывал ранее. Процессы и потоки, например, требуют для себя физической памяти, виртуальной памяти и памяти пула, так что число процессов и потоков, которые могут быть созданы на данной системе Windows, в конечном счете, определяется одним из этих ресурсов, в зависимости от того, каким образом эти процессы или потоки были созданы и какое из ограничений базовых ресурсов будет достигнуто первым. Поэтому я рекомендую вам, чтобы вы прочитали мои предыдущие статьи, если вы до сих пор этого не сделали, потому что далее я буду обращаться к таким понятиям, как зарезервированная память, выделенная память и системное ограничение памяти, о которых я говорил в предыдущих своих статьях:

Процессы и потоки
Процесс Windows по своей сути является контейнером, в котором хранится код команд из исполняемого файла. Он представляет собой объект процесса ядра и Windows использует этот объект процесса и связанные с ним структуры данных для хранения и сопровождения информации об исполняемом коде приложения. Например, процесс имеет виртуальное адресное пространство, в котором хранятся его частные и общие данные и в которое отображаются исполняемый образ и связанные с ним библиотеки DLL. Windows с помощью инструментов диагностики записывает информацию об использовании процессом ресурсов для обеспечения учета и выполнения запросов и регистрирует ссылки процесса на объекты операционной системы в таблице дескриптора процесса. Процессы работают с контекстом безопасности, именуемом маркером, который идентифицирует учетную запись пользователя, группы учетной записи и привилегии, назначенные процессу.

Процесс включает в себя один или более потоков, которые фактически выполняют код в процессе (технически, выполняются не процессы, а потоки) и представлены в системе в виде объектов потоков ядра. Есть несколько причин, почему приложения создают потоки в дополнение к их исходному начальному потоку: 1) процессы, обладающие пользовательским интерфейсом, обычно создают потоки для того, чтобы выполнять свою работу и при этом сохранять отзывчивость основного потока к командам пользователя, связанными с вводом данных и управлением окнами; 2) приложения, которые хотят использовать несколько процессоров для масштабирования производительности или же которые хотят продолжать работать, в то время как потоки останавливают свою работу, ожидая синхронизации операций ввода/вывода, создают потоки, чтобы получить дополнительную выгоду от многопоточной работы.

Ограничения потоков
Помимо основной информации о потоке, включая данные о состоянии регистров ЦП, присвоенный потоку приоритет и информацию об использовании потоком ресурсов, у каждого потока есть выделенная ему часть адресного пространства процесса, называемая стеком, которую поток может использовать как рабочую память по ходу исполнения кода программы, для передачи параметров функций, хранения локальных переменных и адресов результатов работы функций. Таким образом, чтобы избежать нерациональной траты виртуальной памяти системы, первоначально распределяется только часть стека, или же часть ее передается потоку, а остаток просто резервируется. Поскольку стеки в памяти растут по нисходящей, система размещает так называемые "сторожевые" страницы (от англ. guard pages) памяти вне выделенной части стека, которые обеспечивают автоматическое выделение дополнительной памяти (называемой расширением стека), когда она потребуется. На следующей иллюстрации показано, как выделенная область стека углубляется и как сторожевые страницы перемещаются по мере расширения стека в 32-битном адресном пространстве:

Структуры Portable Executable (PE) исполняемых образов определяют объем адресного пространства, которое резервируется и изначально выделяется для стека потока. По умолчанию компоновщик резервирует 1Мб и выделяет одну страницу (4Кб), но разработчики могут изменять эти значения либо меняя значения PE, когда они организуют связь со своей программой, либо путем вызова для отдельного потока функции CreateTread . Вы можете использовать утилиту, такую как Dumpbin , которая идет в комплекте с Visual Studio, чтобы посмотреть настройки исполняемой программы. Вот результаты запуска Dumpbin с опцией /headers для исполняемой программы, сгенерированной новым проектом Visual Studio:

Переведя числа из шестнадцатеричной системы исчисления, вы можете увидеть, что размер резерва стека составляет 1Мб, а выделенная область памяти равна 4Кб; используя новую утилиту от Sysinternals под названием MMap , вы можете подключиться к этому процессу и посмотреть его адресное пространство, и тем самым увидеть изначально выделенную страницу памяти стека процесса, сторожевую страницу и остальную часть зарезервированной памяти стека:

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

Ограничения 32-битных потоков
Даже если бы у процесса вообще не было ни кода, ни данных и все адресное пространство могло бы быть использовано под стеки, то 32-битный процесс с установленным по умолчанию адресным пространством в 2 б мог бы создать максимум 2048 потоков. Вот результаты работы программы Testlimit , запущенной в 32-битной Windows с параметром -t (создание потоков), подтверждающие наличие этого ограничения:

Еще раз, так как часть адресного пространства уже использовалась под код и начальную динамическую память, не все 2Гб были доступны для стеков потоков, так что общее количество созданных потоков не смогло достигнуть теоретического предела в 2048 потоков.

Я попробовал запустить Testlimit с дополнительной опцией, предоставляющей приложению расширенное адресное пространство, надеясь, что если уж ему дадут больше 2Гб адресного пространства (например, в 32-битных системах это достигается путем запуска приложения с опцией /3GB или /USERVA для Boot.ini, или же эквивалентной опцией BCD на Vista и позднее increaseuserva), оно будет его использовать. 32-битным процессам выделяется 4Гб адресного пространства, когда они запускаются на 64-битной Windows, так сколько же потоков сможет создать 32-битный Testlimit, запущенный на 64-битной Windows? Если основываться на том, что мы уже обсудили, ответ должен быть 4096 (4Гб разделенные на 1Мб), однако на практике это число значительно меньше. Вот 32-битный Testlimit, запущенный на 64-битной Windows XP:

Причина этого несоответствия кроется в том факте, что когда вы запускаете 32-битное приложение на 64-битной Windows, оно фактические является 64-битным процессом, которое выполняет 64-битный код от имени 32-битных потоков, и потому в памяти для каждого потока резервируются области под 64-битные и 32-битные стеки потоков. Для 64-битного стека резервируется 256Кб (исключения составляют ОС, вышедшие до Vista, в которых исходный размер стека 64-битных потоков составляет 1Мб). Поскольку каждый 32-битный поток начинает свое существование в 64-битном режиме и размер стека, который ему выделяется при старте, превышает размер страницы, в большинстве случаев вы увидите, что под 64-битный стек потока выделяется как минимум 16Кб. Вот пример 64-битных и 32-битных стеков 32-битного потока (32-битный стек помечен как "Wow64"):

32-битный Testlimit смог создать в 64-битной Windows 3204 потока, что объясняется тем, что каждый поток использует 1Мб + 256Кб адресного пространство под стек (повторюсь, исключением являются версии Windows до Vista, где используется 1Мб+ 1Мб). Однако, я получил другой результат, запустив 32-битный Testlimit на 64-битной Windows 7:

Различия между результатами на Windows XP и Windows 7 вызвано более беспорядочной природой схемы распределения адресного пространства в Windows Vista, Address Space Layout Randomization (ASLR), которая приводит к некоторой фрагментации. Рандомизация загрузки DLL, стека потока и размещения динамической памяти, помогает улучшить защиту от вредоносного ПО. Как вы можете увидеть на следующем снимке программы VMMap, в тестовой системе есть еще 357Мб доступного адресного пространства, но наибольший свободный блок имеет размер 128Кб, что меньше чем 1Мб, необходимый для 32-битного стека:

Как я уже отмечал, разработчик может переустановить заданный по умолчанию размер резерва стека. Одной из возможных причин для этого может быть стремление избежать напрасного расхода адресного пространства, когда заранее известно, что стеком потока всегда будет использоваться меньше, чем установленный по умолчанию 1Мб. PE-образ Testlimit по умолчанию использует размер резерва стека в 64Кб, и когда вы указываете вместе параметром -t параметр -n, Testlimit создает потоки со стеками размером в 64Кб. Вот результат работы этой утилиты на системе с 32-битной Windows XP и 256Мб RAM (я специально провел этот тест на слабой системе, что подчеркнуть данное ограничение):

Здесь следует отметить, что произошла другая ошибка, из чего следует, что в данной ситуации причиной является не адресное пространство. Фактически, 64Кб-стеки должны обеспечить приблизительно 32 000 потоков (2Гб/64Кб = 32768). Так какое же ограничение проявилось в данном случае? Если посмотреть на возможных кандидатов, включая выделенную память и пул, то никаких подсказок в нахождении ответа на этот вопрос они не дают, поскольку все эти значения ниже их пределов:

Ответ мы можем найти в дополнительной информации о памяти в отладчике ядра, который укажет нам искомое ограничение, связанное с доступной резидентной памятью, весь объем которой был исчерпан:

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

Базовый стек ядра занимает 12Кб в 32-битной Windows и 24Кб в 64-битной Windows. 14225 потоков требуют для себя приблизительно 170Мб резидентной памяти, что точно соответствует объему свободной памяти на этой системе с выключенным Testlimit:

Как только достигается предел доступной системной памяти, многие базовые операции начинают завершаться с ошибкой. Например, вот ошибка, которую я получил, дважды кликнув на ярлыке Internet Explorer, расположенном на рабочем столе:

Как и ожидалось, работая на 64-битной Windows с 256Мб RAM, Testlimit смог создать 6600 потоков - примерно половину от того, сколько потоков эта утилита смогла создать в 32-битной Windows с 256Мб RAM - до того, как исчерпалась доступная память:

Причиной, по которой ранее я употреблял термин "базовый" стек ядра, является то, что поток, который работает с графикой и функциями управления окнами, получает "большой" стек, когда он исполняет первый вызов, размер которого равен (или больше) 20Кб на 32-битной Windows и 48Кб на 64-битной Windows. Потоки Testlimit не вызывают ни одного подобного API, так что они имеют базовые стеки ядра.
Ограничения 64-битных потоков

Как и у 32-битных потоков, у 64-битных потоков по умолчанию есть резерв в 1Мб для стека, но 64-битные имеют намного больше пользовательского адресного пространства (8Тб), так что оно не должно стать проблемой, когда дело доходит до создания большого количества потоков. И все же очевидно, что резидентная доступная память по-прежнему является потенциальным ограничителем. 64-битная версия Testlimit (Testlimit64.exe) смогла создать с параметром -n и без него приблизительно 6600 потоков на системе с 64-битной Windows XP и 256Мб RAM, ровно столько же, сколько создала 32-битная версия, потому что был достигнут предел резидентной доступной памяти. Однако, на системе с 2Гб оперативной памяти Testlimit64 смог создать только 55000 потоков, что значительно меньше того количества потоков, которое могла бы создать эта утилита, если бы ограничением выступила резидентная доступная память (2Гб/24Кб = 89000):

В данном случае причиной является выделенный начальный стек потока, который приводит к тому, что в системе заканчивается виртуальная память и появляется ошибка, связанная с нехваткой объема файла подкачки. Как только объем выделенной памяти достигает размера оперативной памяти, скорость создания новых потоков существенно снижается, потому что система начинает "пробуксовывать", ранее созданные стеки потоков начинают выгружаться в файл подкачки, чтобы освободить место для стеков новых потоков, и файл подкачки должен увеличиваться. С включенным параметром -n результаты те же, поскольку таким же остается начальный объем выделенной памяти стека.

Ограничения процессов
Число процессов, поддерживаемых Windows, очевидно, должно быть меньше, чем число потоков, потому как каждый процесс имеет один поток и сам по себе процесс приводит к дополнительному расходу ресурсов. 32-битный Testlimit, запущенный на системе с 64-битной Windows XP и 2Гб системной памяти создает около 8400 процессов:

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

Если бы процесс использовал резидентную доступную память для размещения только лишь стека потока привилегированного режима, Testlimit смог бы создать намного больше, чем 8400 потоков на системе с 2Гб. Количество резидентной доступной памяти на этой системе без запущенного Testlimit равно 1,9Гб:

Путем деления объема резидентной памяти, используемой Testlimit (1,9Гб), на число созданных им процессов получаем, что на каждый процесс отводится 230Кб резидентной памяти. Так как 64-битный стек ядра занимает 24 Кб, мы получаем, что без вести пропали примерно 206Кб для каждого процесса. Где же остальная часть используемой резидентной памяти? Когда процесс создан, Windows резервирует достаточный объем физической памяти, чтобы обеспечить минимальный рабочий набор страниц (от англ. working set). Это делается для того, чтобы гарантировать процессу, что любой ситуации в его распоряжении будет достаточное количество физической памяти для сохранения такого объема данных, который необходим для обеспечения минимального рабочего набора страниц. По умолчанию размер рабочего набора страниц зачастую составляет 200Кб, что можно легко проверить, добавив в окне Process Explorer столбец Minimum Working Set:

Оставшиеся 6Кб - это резидентная доступная память, выделяемая под дополнительную нестраничную память (от англ. nonpageable memory), в которой хранится сам процесс. Процесс в 32-битной Windows использует чуть меньше резидентной памяти, поскольку его привилегированный стек потока меньше.

Как и в случае со стеками потока пользовательского режима, процессы могут переопределять установленный для них по умолчанию размер рабочего набора страниц с помощью функции SetProcessWorkingSetSize . Testlimit поддерживает параметр -n, который, в совокупности с параметром -p, позволяет устанавливать для дочерних процессов главного процесса Testlimit минимально возможный размер рабочего набора страниц, равный 80Кб. Поскольку дочерним процессам нужно время, чтобы сократить их рабочие наборы страниц, Testlimit, после того, как он больше не сможет создавать процессы, приостанавливает работу и пробует ее продолжить, давая его дочерним процессам шанс выполниться. Testlimit, запущенный с параметром -n на системе с Windows 7 и 4Гб RAM уже другого, отличного от ограничения резидентной доступной памяти, предела - ограничения выделенной системной памяти:

На снимке снизу вы можете увидеть, что отладчик ядра сообщает не только о том, что был достигнут предел выделенной системной памяти, но и о том, что, после достижения этого ограничения, имели место тысячи ошибок распределения памяти, как виртуальной, так и памяти, выделенной под выгружаемый пул (предел выделенной системной памяти фактически был достигнут несколько раз, так как, когда случалась ошибка, связанная с нехваткой объема файла подкачки, этот самый объем увеличивался, отодвигая это ограничение):

До запуска Testlimit средний уровень выделенного объема памяти был равен приблизительно 1,5Гб, так что потоки заняли около 8Гб выделенной памяти. Следовательно, каждый процесс потреблял примерно 8 Гб/6600 или 1,2Мб. Результат выполнения команды!vm отладчика ядра, которая показывает распределение собственной памяти (от англ. private memory) для каждого процесса, подтверждает верность данного вычисления:

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

Сколько процессов и потоков будет достаточно?
Таким образом, ответы на вопросы "сколько потоков поддерживает Windows?" и "сколько процессов вы можете одновременно запустить на Windows?" взаимосвязаны. Помимо нюансов методов, по которым потоки определяют размер их стека и процессы определяют их минимальный рабочий набор страниц, двумя главными факторами, определяющим ответы на эти вопросы для каждой конкретной системы, являются объем физической памяти и ограничение выделенной системной памяти. В любом случае, если приложение создает достаточное количество потоков или процессов, чтобы приблизиться к этим пределам, то его разработчику следует пересмотреть проект этого приложения, поскольку всегда существуют различные способы достигнуть того же результата с разумным числом процессов. Например, основной целью при масштабировании приложения является стремление сохранить число выполняющихся потоков равным числу ЦП, и один из способов добиться этого состоит в переходе от использования синхронных операции ввода/вывода к асинхронным с использованием портов завершения, что должно помочь сохранить соответствие числа запущенных потоков с числом ЦП.

Эта статья также доступна на следующих языках: Тайский

  • Next

    Огромное Вам СПАСИБО за очень полезную информацию в статье. Очень понятно все изложено. Чувствуется, что проделана большая работа по анализу работы магазина eBay

    • Спасибо вам и другим постоянным читателям моего блога. Без вас у меня не было бы достаточной мотивации, чтобы посвящать много времени ведению этого сайта. У меня мозги так устроены: люблю копнуть вглубь, систематизировать разрозненные данные, пробовать то, что раньше до меня никто не делал, либо не смотрел под таким углом зрения. Жаль, что только нашим соотечественникам из-за кризиса в России отнюдь не до шоппинга на eBay. Покупают на Алиэкспрессе из Китая, так как там в разы дешевле товары (часто в ущерб качеству). Но онлайн-аукционы eBay, Amazon, ETSY легко дадут китайцам фору по ассортименту брендовых вещей, винтажных вещей, ручной работы и разных этнических товаров.

      • Next

        В ваших статьях ценно именно ваше личное отношение и анализ темы. Вы этот блог не бросайте, я сюда часто заглядываю. Нас таких много должно быть. Мне на эл. почту пришло недавно предложение о том, что научат торговать на Амазоне и eBay. И я вспомнила про ваши подробные статьи об этих торг. площ. Перечитала все заново и сделала вывод, что курсы- это лохотрон. Сама на eBay еще ничего не покупала. Я не из России , а из Казахстана (г. Алматы). Но нам тоже лишних трат пока не надо. Желаю вам удачи и берегите себя в азиатских краях.

  • Еще приятно, что попытки eBay по руссификации интерфейса для пользователей из России и стран СНГ, начали приносить плоды. Ведь подавляющая часть граждан стран бывшего СССР не сильна познаниями иностранных языков. Английский язык знают не более 5% населения. Среди молодежи — побольше. Поэтому хотя бы интерфейс на русском языке — это большая помощь для онлайн-шоппинга на этой торговой площадке. Ебей не пошел по пути китайского собрата Алиэкспресс, где совершается машинный (очень корявый и непонятный, местами вызывающий смех) перевод описания товаров. Надеюсь, что на более продвинутом этапе развития искусственного интеллекта станет реальностью качественный машинный перевод с любого языка на любой за считанные доли секунды. Пока имеем вот что (профиль одного из продавцов на ебей с русским интерфейсом, но англоязычным описанием):
    https://uploads.disquscdn.com/images/7a52c9a89108b922159a4fad35de0ab0bee0c8804b9731f56d8a1dc659655d60.png