Faq как передать при создании нити (tthread) ей некоторое значение


Содержание
Илон Маск рекомендует:  Что такое код asp exitmessage

Программирование: Разработка и отладка программ

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

Атрибуты нити хранятся в объекте со скрытой реализацией — объекте атрибутов нити , который используется при создании нити. Этот объект хранит набор атрибутов, зависящий от реализации опций POSIX. Доступ к нему осуществляется через переменную типа pthread_attr_t . В AIX тип данных pthread_attr_t обозначает указатель; в других системах это может быть структура или иной тип данных.

Объект атрибутов нити инициализируется значениями по умолчанию с помощью процедуры pthread_attr_init . Для работы с атрибутами предназначены специальные функции. Объект атрибутов нити удаляется с помощью процедуры pthread_attr_destroy . Эта процедура может освобождать память, выделенную процедурой pthread_attr_init , в зависимости от реализации библиотеки нитей.

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

Один объект атрибутов может применяться для создания нескольких нитей. Между вызовами операции создания нитей атрибуты объекта можно изменить. После создания нитей объект атрибутов можно удалить — эта операция не повлияет на работу созданных нитей.

Описанный ниже атрибут определен всегда.

Detachstate Определяет состояние запуска нити.

Значение атрибута может быть считать процедурой pthread_attr_getdetachstate и установить процедурой pthread_attr_setdetachstate . Атрибут может принимать значение одной из следующих констант:

PTHREAD_CREATE_DETACHED Указывает, что нить будет создана в автономном режиме.
PTHREAD_CREATE_JOINABLE Указывает, что нить будет создана в подключаемом режиме.

Значение по умолчанию — PTHREAD_CREATE_JOINABLE .

Если нить создается в подключаемом состоянии, для нее должна быть вызвана процедура pthread_join (Вызов функции pthread_join). В противном случае, в системе может оказаться недостаточно памяти для создания новой нити, так как каждая нить занимает относительно большой объем.

Ниже перечислены другие атрибуты, определенные в AIX. Они предназначены для более точного управления нитями, и для их применения могут потребоваться специальные права доступа. Большинство программ нормально работают со значениями по умолчанию.

Область действия Задает область действия нити.
Inheritsched Задает параметры наследования атрибутов планировщика нити.
Schedparam Задает параметры планировщика для нити.
Schedpolicy Задает стратегию планирования для нити.

Применение этих атрибутов описано в разделе Атрибуты планировщика.

Stacksize Задает размер стека нити.
Stackaddr Указывает адрес стека нити.
Guardsize Определяет размер контрольной области для стека нити.

Применение этих атрибутов описано в разделе Атрибуты стека.

Для создания нити предназначена процедура pthread_create . Эта процедура создает новую нить и запускает ее.

В вызове процедуры pthread_create можно указать объект атрибутов нити. Если задан указатель NULL , нить создается со значением атрибутов по умолчанию. Следовательно, фрагмент кода:

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

Процедура точки входа имеет один параметр — указатель типа void, который задается при вызове pthread_create . Он может применяться для передачи указателя на некоторые данные — например, на строку или структуру. Создающая (вызывающая процедуру pthread_create ) и создаваемая нить должны согласовать фактический тип этого указателя.

Процедура точки входа возвращает указатель типа void. После завершения нити этот указатель хранится библиотекой нитей до момента удаления нити. Дополнительная информация о работе с этим указателем приведена в разделе Обзор синхронизации.

Процедура pthread_create возвращает идентификатор новой нити. Вызывающая нить может использовать этот идентификатор для выполнения различных действий с созданной нитью.

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

Если процедура pthread_create не может создать нить, то параметр thread содержит недопустимый идентификатор, а процедура возвращает код ошибки.

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

Идентификатор нити — это объект со скрытой реализацией типа pthread_t . В AIX pthread_t совпадает с типом int. В других системах он может быть структурой, указателем или другим типом.

Для повышения переносимости программ, использующих библиотеку нитей, ИД нити должен всегда обрабатываться как объект со скрытой реализацией. По этой причине сравнение идентификаторов нитей должно выполняться с помощью процедуры pthread_equal . Не используйте оператор сравнения C ( == ), так как тип данных pthread_t может отличаться от арифметического типа данных или указателя.

Ниже приведено краткое описание простой многонитевой программы. Она выводит приветствие на английском и французском языках на 5 секунд. Эта программа должна быть откомпилирована с опцией cc_r или xlc_r . Дополнительная информация о компиляции многонитевых программ приведена в разделе Создание программ с несколькими нитями.

Исходная нить (в которой выполняется функция main ) создает две нити. Обе нити используют одну процедуру точки входа ( Thread ), но с разными параметрами. В качестве параметра передается указатель на выводимую строку.

Чем отличаются потоки от нитей?

Вроде как должно быть одно и тоже, так как thread переводят и как «поток», и как «нить». Вроде как, при создании потока (нити) и в послед запуске он может разбиваться на нити?

Это поток порождает себе подобных (потоки такие же как и он) или же он порождает нити (которые видимо являются неполноценными потоками)?

А еще, являются ли потоки чтения (с консоли, файла) и вывода (в файл) такими же потоками?

Почему тогда мы не вызываем у них методы start и пр., а только close ?

Или же можно запустить нить создав лишь объект их типа?

Неужели это все одни и те же потоки? И еще, наличие 2-х нитей c thread и с runnable обусловлено тем, что с 1-ым нельзя наследоваться, но легче запускать, а со 2м можно наследоваться, но сложнее запускать?


2 ответа 2

Вроде как должно быть одно и тоже , тк thread переводят и так , и так.

Одно и то же. Вот если встретите термин Fiber — там все немного интересней, взаимодействие с ним происходит как с потоком, но классическим ОС-потоком он не является.

Вроде как , при создании потока(нити) и в послед запуске он может разбиваться на нити?

Разбиваться не может, порождать — может. Порожденные потоки имеют ровно те же возможности.

А еще , являются ли потоки чтения(с консоли,файла) и вывода(в файл) такими же потоками?

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

И еще , наличие 2х нитей c thread и с runnable обусловлено тем , что с 1ым нельзя наследоваться , но легче запускать ,а со 2м можно наследоваться , но сложнее запускать?

не берусь судить за архитекторов API, но наследоваться от класса потока вам не стоит вообще никогда. В большинстве случаев вам так вообще потребуется ExecutorService, который возьмет на себя управление потоками, а вы будете просто отдавать ему задачи на выполнение в виде Runnable или аналога.

Faq как передать при создании нити (tthread) ей некоторое значение?

procedure LaunchYourThread(const AFileName: String);

procedure YourTreadTerminate(Sender: TObject);

procedure TYourForm.LaunchYourThread(const AFileName: String);

YourThread := TYourThread.Create(True, AFileName);

procedure TYourForm.YourTreadTerminate(Sender: TObject);

СGI программа должна показывать GIF изображение.

Имею тег. Прочитать JPeg, указать ContentType=Image/jpeg и выдать изображение в SaveToStream умею. Как сделать тоже самое для файлов GIF, в особенности анимационных? Если можно просто перелить дисковый файл (пусть он хоть трижды GIF) в Response CGI-програмы, то как это сделать?

Выдайте из скрипта следующее:

Советы по работе с реестром.

Использование некоторых ключей реестра

Добавление элементов в контекстное меню «Создать»

1. Создать новый документ, поместить его в папку Windows/ShellNew

2. В редакторе реестра найти расширение этого файла, добавить новый подключ, добавить туда строку: FileName в качестве значения которой указать имя созданного файла.

Путь к файлу который открывает не зарегистрированные файлы

1. Найти ключ HKEY_CLASSES_ROOT\Unknown\Shell

2. Добавить новый ключ Open

3. Под этим ключом еще ключ с именем command в котором изменить значение (По умолчанию) на имя запускаемого файла, к имени нужно добавить %1. (Windows заменит этот символ на имя запускаемого файла)

В проводнике контекстное меню «Открыть в новом окне»

1. Найти ключ HKEY_CLASSES_ROOT\Directory\Shell

2. Создать подключ: opennew в котором изменить значение (По умолчанию) на: «Открыть в новом окне»

3. Под этим ключом создать еще подключ command (По умолчанию) = explorer %1

Использование средней кнопки мыши Logitech в качестве двойного щелчка


Подключ HKEY_LOCAL_MACHINE\SoftWare\Logitech и там найти параметр DoubleClick заменить 000 на 001

Новые звуковые события

Например создает звуки на запуск и закрытие WinWord

HKEY_CURRENT_USER\AppEvents\Shemes\Apps добавить подключ WinWord и к нему подключи Open и Close.

Теперь в настройках звуков видны новые события

Путь в реестре для деинсталяции программ:

Работа с реестром в Delphi 1

В Delphi 2 и выше появился объект TRegistry при помощи которого очень просто работать с реестром. Но мы здесь рассмотрим функции API, которые доступны и в Delphi 1.

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

Для работы с реестром применяется ряд функций API :

Как пользоваться QThread в Qt

В статье рассказывается о неправильном и более правильном способах использования механизма потоков через QThread в Qt.

20.02.2020 2 комментария 35 120 просмотров

В статье рассказывается о неправильном и более правильном способах использования механизма потоков через QThread в Qt.

Содержание

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

Неправильный способ

Самый простой в реализации способ — создать потомка от класса QThread и переопределить метод run . Но обратите внимание, данный способ не соответствует идеологии ООП и многие его не рекомендуют.

Создадим приложение, в котором при нажатии кнопки должна запуститься функция, которая увеличивает счетчик на 1 каждую секунду, а остальное время спит. При этом в textEdit выводится информация о состоянии счетчика.

Внешний вид приложения будет таким:

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

Итак, создадим новый класс MyThread.

mycalss.cpp

mycalss.h

Что у нас тут есть?

  1. Класс наследуется от класса QThread , а также обладает макросом Q_OBJECT . Без него не будут работать сигналы и слоты.
  2. Конструктор потока теперь принимает параметры, например, имя потока. Таким образом в поток можно передать нужную информацию до его запуска.
  3. Основная деятельность потока реализована в методе run() .
  4. У нас есть сигнал void send(int) , который из потока отправляется при каждом изменении счетчика i в методе run() :

Для того, чтобы сигнал из потока получать в главном потоке, нам нужен слот, который будет данный сигнал принимать и обрабатывать. Поэтому в mainwindow.cpp пропишем слот:

А в mainwindow.h объявим его:

Не забудьте в mainwindow.cpp подключить инклуд:

Теперь в слоте нажатия на кнопку мы можем создать экземпляр нашего слота, соединить слот update и сигнал send , а потом уже запустить наш поток.

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

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


В примере ниже мы добавим переменную int globalVar в класс, которую и поменяем из главного потока.

Обрабатывать сигнал будем слотом receiveGlobalVar(int globalVar) .

mycalss.cpp

mycalss.h

И в mainwindow.h добавим сигнал:

Соединяем слот и сигнал (обратите внимание на объявление кто отправитель сигнала, а кто получатель), а потом можем сигнал отправлять:

Вот полный код нажатия на кнопку:

При запуске программы и нажатии на кнопку мы увидим, что сигнал из главного потока в новый поток прошел успешно:

Более правильный способ

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

К тому же, как в примере выше, нам пришлось добавлять переменные в класс (globalVar), чтобы можно было получить к ним доступ из разных методов и слотов данного класса. Это всё не очень красиво и не соответствует духу ООП.

Куда правильнее будет прописать класс с нужным функционалом, создать его экземпляр и запихать его в экземпляр нового потока QThread . Что мы и сделаем.

Создание и завершение нитей

4. Принудительное завершение нити (прерывание)

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

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

Момент, в который нить получает сообщение о попытке прервать ее исполнение функцией pthread_cancel(3C) контролируется атрибутами нити, известными как cancel state и cancel type .

Cancel state (состояние прерывания) определяет, разрешено ли прерывание нити как таковое. Т.е. этот атрибут может иметь два значения – разрешено или запрещено. Если прерывание разрешено, нить немедленно получает сообщение о попытке ее прервать (хотя, в зависимости от cancel type , может отреагировать на это сообщение лишь через некоторое время). Если прерывание запрещено, попытки прерывания нити накапливаются. После того, как прерывания все-таки разрешат, нить получит сигналы о накопившихся попытках.

Переключение состояния прерывания осуществляется функцией pthread_setcancelstate(3C) .

Первый параметр этой функции входной и может принимать значения PTHREAD_CANCEL_ENABLE ( прерывание разрешено) и PTHREAD_CANCEL_DISABLE ( прерывание запрещено). Эти значения – препроцессорные макроопределения , содержащиеся в файле pthread.h . Вызов функции с другими значениями первого параметра приведет к ошибке EINVAL . Второй параметр функции – выходной, содержит указатель на переменную, в которой будет размещено старое значение типа прерывания. В качестве этого указателя можно передать NULL , в этом случае старое значение состояния будет потеряно. По умолчанию, нить создается с разрешенными прерываниями.

Cancel type (тип прерывания) определяет, в какие моменты нить проверяет сообщения о прерываниях. Этот атрибут может принимать два значения – PTHREAD_CANCEL_DEFERRED (отложенное прерывание ) и PTHREAD_CANCEL_ASYNCHRONOUS (асинхронное прерывание ). По умолчанию, нить создается с отложенным типом прерываний. Что означает каждое из возможных значений этого атрибута, описывается далее в этом разделе.

Установка типа прерывания осуществляется функцией pthread_setcanceltype(3C) . Схема передачи параметров этой функции аналогична pthread_setcancelstate(3C) .

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

Асинхронное прерывание означает, что библиотека прерывает нить как можно скорее (хотя во многих ситуациях не удается гарантировать, чтобы это происходило точно в тот момент, когда другая нить вызвала pthread_cancel(3С) ). Асинхронное прерывание требует тщательного анализа всех возможных моментов, когда оно может произойти, и обработки всех ситуаций, связанных с прерываниями в неудачные моменты. Так, если прерывание произойдет во время работы с библиотекой, которая не считается thread-safe , внутренние данные этой библиотеки могут остаться в несогласованном состоянии.

Для корректной работы большинства библиотек в таких условиях простой поддержки многопоточности не достаточно. В страницах системного руководства Solaris уровень поддержки многопоточности описывается несколькими типами атрибутов, которые рассматриваются в «лекции 5» ; безопасность библиотеки или функции при использовании асинронных прерываний описывается атрибутом MT-Level. У библиотеки, которая безопасна для применения в таких условиях, этот атрибут имеет значение Asynchronous- Cancel -Safe. Для всех библиотек и функций, для которых этот атрибут явным образом не указан на соответствующей странице системного руководства, следует предполагать, что они небезопасны для использования в режиме асинхронных прерываний.

Отложенное прерывание означает, что нить получает сообщение о прерывании лишь в определенные моменты, известные как точки прерывания ( cancellation point ). Эти точки, в свою очередь , делятся на две категории – явные и неявные. Явные точки прерывания – это вызовы функции pthread_testcancel(3C) . Неявные точки прерывания – это вызовы следующих функций и системных вызовов:

Список приведен для Solaris 10. Получить полный список неявных точек прерывания для вашей версии ОС можно получить на странице руководства cancellation(5) . Несколько забегая вперед, необходимо отметить, что функция pthread_mutex_lock(3С) не является неявной точкой прерывания, и в соответствии с требованиями стандарта POSIX , не должна являться таковой.

Стандарт POSIX допускает наличие неявных точек прерывания в некоторых других системных вызовах и библиотечных функциях, которые перечислены на странице http://www.opengroup.org/onlinepubs/007908799/xsh/threads.html. Слушатели, знакомые с реализацией стандартных библиотек языка C должны заметить, что это преимущественно функции, которые содержат или могут содержать вызовы неявных точек прерывания, перечисленных выше. В частности, это большинство функций буферизованного ввода-вывода ( printf(3C), fread (3C), fwrite(3C) ).

Однако реализации POSIX Thread Library в Solaris 10 и Linux 2.6 не содержат неявных точек прерывания ни в одной из этих функций. Во всяком случае, в Solaris 10 список функций, содержащих неявные точки прерывания, приведенный на странице руководства cancellation(5) , является исчерпывающим.

Слушателям, интересующимся вопросом, как удалось реализовать printf(3C) или fwrite(3C) без обращения к точке прерывания в теле write(2) , я рекомендую обратиться к исходным текстам библиотеки языка C, размещенным на сайте http://www.opensolaris.org, или просто пройти в пошаговом отладчике код библиотечной функции fwrite(3C) . Для этого предварительно рекомендуется выключить буферизацию функцией setvbuf(3C) , тогда каждый вызов fwrite будет вызывать соответствующий системный вызов лишь после небольшого количества проверок, и будет легче понять логику кода.

Более сложный вопрос – зачем это было сделано. Ответ, скорее всего, состоит в том, что функции буферизованного ввода-вывода – не просто «обертки» над соответствующими системными вызовами, они осуществляют определенные (и иногда довольно сложные) операции над структурой данных, которая, собственно, и является буфером ввода-вывода. Прерывание этих библиотечных функций в неудачный момент либо приводило бы к риску оставить буфер ввода-вывода в несогласованном состоянии, либо требовало бы сложных вычислений для отслеживания и восстановления его согласованности.

6. Обработка прерывания нити


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

которая определена в файле /usr/include/pthread.h . Эта макрокоманда внешне выглядит как вызов одноименной функции с двумя параметрами – указателем на функцию-обработчик и значением, которое следует передать этой функции в качестве параметра.

Функции-обработчики вызываются в стековом порядке ( Last In First Out), т.е. в порядке, обратном тому, в котором они устанавливались, и из того контекста, в котором они устанавливались. Иными словами, в качестве параметров функций-обработчиков можно использовать указатели на локальные переменные, определенные в тех блоках, в которых стоял вызов соответствующего pthread_cleanup_push(3C) .

Каждый вызов pthread_cleanup_push(3C) должен сопровождаться соответствующим вызовом парной макрокоманды pthread_cleanup_pop(3C) . Эта макрокоманда должна вызываться в пределах того же блока, в котором была вызвана соответствующая pthread_cleanup_push(3C) .

Нарушение этого требования приведет к ошибке компиляции. В действительности, макрокоманды pthread_cleanup_push/pop содержат, соответственно, открывающую и закрывающую фигурную скобки. Это не является требованием стандарта POSIX , но поможет понять, к каким именно ошибкам компиляции может привести их неправильное использование.

Макроопределение pthread_cleanup_pop(3С) содержит параметр , который указывает, следует ли вызвать обработчик при его выталкивании из стека или нет. Если обработчик осуществляет какие-то сложные неатомарные операции , рекомендуется его вызывать, а не дублировать соответствующий код в теле основной функции нити.

Не следует использовать в функциях-обработчиках такие функции, как lonjmp(3C)/siglongjmp(3C) или оператор throw языка С++, это может привести, и в большинстве реализаций действительно приведет, к нарушению порядка вызова других обработчиков или даже к разрушению стека и аварийному завершению программы. В большинстве реализаций другие обработчики просто не будут вызваны.

Средства System V IPC. Организация работы с разделяемой памятью в UNIX. Понятие нитей исполнения (thread)

Понятие о нити исполнения (thread) в UNIX. Идентификатор нити исполнения. Функция pthread_self()

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

В различных версиях операционной системы UNIX существуют различные интерфейсы, обеспечивающие работу с нитями исполнения . Мы кратко ознакомимся с некоторыми функциями, позволяющими разделить процесс на thread ‘ы и управлять их поведением, в соответствии со стандартом POSIX . Нити исполнения , удовлетворяющие стандарту POSIX , принято называть POSIX thread ‘ами или, кратко, pthread’ами.

К сожалению, операционная система Linux не полностью поддерживает нити исполнения на уровне ядра системы. При создании нового thread ‘а запускается новый традиционный процесс, разделяющий с родительским традиционным процессом его ресурсы, программный код и данные, расположенные вне стека, т.е. фактически действительно создается новый thread , но ядро не умеет определять, что эти thread ‘ы являются составными частями одного целого. Это «знает» только специальный процесс-координатор, работающий на пользовательском уровне и стартующий при первом вызове функций, обеспечивающих POSIX интерфейс для нитей исполнения . Поэтому мы сможем наблюдать не все преимущества использования нитей исполнения (в частности, ускорить решение задачи на однопроцессорной машине с их помощью вряд ли получится), но даже в этом случае thread ‘ы можно задействовать как очень удобный способ для создания процессов с общими ресурсами, программным кодом и разделяемой памятью .

Каждая нить исполнения , как и процесс, имеет в системе уникальный номер – идентификатор thread’a. Поскольку традиционный процесс в концепции нитей исполнения трактуется как процесс, содержащий единственную нить исполнения , мы можем узнать идентификатор этой нити и для любого обычного процесса. Для этого используется функция pthread_self() . Нить исполнения , создаваемую при рождении нового процесса, принято называть начальной или главной нитью исполнения этого процесса.

Лекция 3 Создание и завершение нитей

1. Создание нитей с атрибутами по умолчанию

2. Передача параметров нити

3. Завершение нити

Протестировать взаимодействие POSIX Thread API и вашего компилятора C++ можно при помощи программы из примера 2.
Пример 2

Поведение деструкторов локальных переменных в нитях при завершении процесса при помощи exit(2) не описано в стандарте. На практике они не вызываются даже для переменных, описанных в основной нити программы, и даже в однопоточных программах (проверялось в Solaris и Linux на всех доступных версиях компиляторов).
В действительности, все еще хуже – при выходе по exit(2) начинают вызываться деструкторы статических и глобальных переменных, но нити продолжают работу – в некоторых случаях это может приводить к аварийному завершению программы. Так, если в программе примера 2 в строке 42 закомментировать pthread_exit, то программа, собранная Sun Studio 11, с высокой вероятностью завершается по Segmentation Fault (попытка вывода в std::cout после того, как деструктор std::cout уже отработал). Поэтому если вы хотите экстренно завершить многопоточную программу, написанную на C++, используйте _exit(2). Если вам необходимо обеспечить вызов деструкторов локальных переменных, следует использовать исключения С++.
Хотя для ряда платформ обеспечивается удовлетворительное взаимодействие POSIX Thread library со средой исполнения С++, написание многоплатформенных многопоточных приложений на C++ может оказаться сложной задачей. Во многих случаях для написания переносимых многопоточных приложений на C++ лучше использовать не непосредственно POSIX Threads, а BOOST threads.
К передаче выходного значения нити относятся все те же самые соображения, что и к передаче параметров нити. Хотя выходное значение нити описано как void *, библиотека никогда не обращается к нему как к указателю, поэтому в качестве значения можно возвращать ноль или другие скалярные значения.
При передаче указателей возникают те же проблемы, что и при передаче параметров – опасность возникновения висячих ссылок и опасность утечки памяти. Ни в коем случае нельзя возвращать указатели на локальные переменные и другие объекты, созданные в стеке нити, потому что нить, получающая возвращаемое значение функцией pthread_join(3C) (рассматривается ниже), получает доступ к значению лишь после того, как стек нити и другие ресурсы, связанные с нитью, в том числе thread local data, уже уничтожены.
При использовании для размещения возвращаемого значения malloc(3C) возникает опасность утечки памяти. Действительно, если для размещения значения использовать malloc(3C), то нить, получающая наше значение, должна его освободить. Но если эта нить получит сигнал принудительного завершения ( pthread_cancel(3C)), то она не дождется нашего завершения и не освободит наше значение.
Универсального решения этой проблемы не существует. В программах с фиксированным или ограниченным числом нитей часто используют для передачи параметров и выходных значений указатели на статические переменные. В программах с переменным числом нитей обычно используют структуру, когда одна нить создает все остальные нити, размещает для них блоки переменных состояния, дожидается завершения каждой из нитей и уничтожает или переиспользует эти блоки. При этом, разумеется, логично, чтобы нить получала указатель на свой блок переменных состояния как параметр, работала с ним в течении всего срока жизни и возвращала этот же блок в качестве параметра pthread_exit(3C). Но эта архитектура пригодна не для всех приложений.

4. Ожидание завершения нити

Для ожидания завершения нити и получения ее кода возврата используется библиотечная функция pthread_join(3C). Эта функция имеет два параметра, идентификатор нити типа pthread_t, и указатель на переменную типа void *, в которой размещается значение кода возврата. Если в качестве второго параметра передать нулевой указатель, код возврата игнорируется.
Если требуемая нить еще не завершилась, то нить, сделавшая вызов pthread_join(3C), блокируется. Если такой нити (уже) не существует, pthread_join(3C) возвращает ошибку ESRCH.
Когда нить завершается, то связанные с ней ресурсы существуют до того момента, пока какая-то другая нить не вызовет pthread_join(3C). Однако к тому моменту, когда pthread_join завершается, все ресурсы, занятые нитью (стек, thread local data, дескриптор нити) уничтожаются.
В отличие от процессов Unix, где системный вызов wait(2) может использовать только родитель по отношению к потомкам, любая нить может ждать завершения любой другой нити того же процесса. Если несколько нитей ждут завершения одной и той же нити, которая еще не завершилась, все эти нити блокируются. Однако при завершении нити одна из ожидавших нитей получает код возврата, а остальные ошибку ESRCH.
Если нить пытается ожидать сама себя, она получает ошибку EDEADLK.
Еще одна важная функция, связанная с ожиданием завершения нити – это функция pthread_detach(3C). Эта функция указывает, что все ресурсы, связанные с нитью, необходимо уничтожать сразу после завершения этой нити. При этом уничтожается и код возврата такой нити – при попытке сделать pthread_join(3C) на нить, над которой перед этим сделали pthread_detach(3C), возвращается код ошибки EINVAL.
В руководстве по pthread_detach(3C) в системе Solaris 10 сказано, что главное применение pthread_detach(3C) – это ситуация, когда родитель, ожидавший завершения дочерней нити, получает pthread_cancel(3C). В действительности, существуют и другие применения «отсоединенных» нитей.
Не обязательно делать pthread_detach(3C) на уже запущенную нить; в атрибутах нити (pthread_attr_t) можно указать, что нить следует запускать в уже «отсоединенном» состоянии. Это рассматривается в следующей лекции.
Разумеется, нить, над которой сделали pthread_detach(3C), не должна выделять память под возвращаемое значение при помощи malloc(3C) – ведь никто не сможет освободить эту память и это приведет к утечке памяти.
Рекомендованного стандартом способа проверить собственную «отсоединенность» нет. Из предыдущего описания можно предположить, что для проверки «отсоединенности» можно было бы использовать код возврата pthread_join(3C) для собственного идентификатора нитей – для «отсоединенных» нитей это должен быть EINVAL, а для «неотсоединенных» – EDEADLK. Для Solaris 10 и Linux 2.6 это действительно так (во всяком случае для Debian Sarge), однако в Linux 2.4 ptread_join(pthread_self(), NULL) всегда возвращает EDEADLK. Как ведет себя pthread_join на вашей системе, можно проверить при помощи программы примера 3.
Кроме нестандартности, данный способ не гарантирует, что вас не «отсоединят» уже после того, как вы осуществили проверку. То есть надежда на результат такой проверки может привести к ошибке соревнования (race condition). Поэтому применять такую проверку в реальных приложениях не следует.
Пример 3

Поскольку стандартного способа проверить «отсоединенность» нити не существует, при разработке архитектуры приложения не следует полагаться на то, что способ этой проверки вообще доступен.

Что такое нити (threads)?

Читают сейчас

Похожие публикации

  • 19 июля 2011 в 22:15

Процессы и потоки в Andro > +18 106k 266 27

История двух нитей

Процессы и потоки in-depth. Обзор различных потоковых моделей

Заказы

AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Комментарии 45

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

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

Да даже в том же Windows. Ресурсы процессора включают в себя не только регистры общего состояния, указатель стэка и указатель инструкции, но и кучу всяких других регистров, которые никак не ‘виртуализируются’. Вот… Поэтому, уважаемый , вы всё же предлагаете слишком простую модель нити, которая от сложной действительности достаточно далека.

А существует необходимость ассоциировать нить именно с процессором? Например, в какой-нибудь системе вроде Erlang или Oz нити могут исполнятся на процессорах разной архитектуры и периодических прыгать по этим самым процессорам без ущерба для функциональности.

И тогда вся теория про то, что нить — это способ виртуализовать процессор ломается. Ведь, возникает вопрос: а какой именно процессор?

Вот. И не понятно, почему вам не нравится определение ‘нить — это контекст для исполнения программы’. Контексты и программы бывают разные, со всеми вытекающими последствиями в виде разнообразного мира.

Я раза три-четыре читал статью, и никак не понять, о чем речь, о какой виртуализации автор пишет, ничего не понимаю.
Во-первых, Поток это Stream,я всем настоятельно рекомендую использовать западную терминологию и не пытаться найти ей русскоязычный аналог. Stream = стрим, скорее всего речь идет о потока ввода/вывода (чтение байтов из файла, чтение массива байтов на сетевом соединении и т.д.).

Thread = тред. Речь идет о… (как-бы это сказать своими словами, чтоб было просто и понятно, и при этом не исказить смысл. ). Тред, это подпрограмма, порожденная основной программой, тред живет, пока живет программа, породившая его. Программа может прекращать работу тредов. Так же тред может работать даже после завершения программы — это тред-демон (daemon-thread), особый тип тредов.
Сама программа, тоже работает в своем, главном треде. Если программа во время своей работы создает треды, они становятся наследниками основного треда.
Соответсвенно, операционная система отвечает за создание тредов, в которых исполняются программы: браузер Хром, MS Word. Эти треды работают параллельно. Как это получается — уже второй вопрос, касающийся архитектуры операционной системы.

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

Практический пример: есть программа, читающая файлы с двух физически разных дисков. Данные должны быть считаны, загружены в память, а затем сохранены.
Если это приложение будет работать только одим тредом, то файлы будут открываться попеременно, то с первого, то со второго диска модулем программы, отвечающим за чтение файлов и выгрузку данных в память.
Есть возможность оптимизировать этот процесс.
Написать модуль чтения данных и загрузки в память таким образом, чтобы одновременно могло быть два таких модуля.
Программа загружает два одинаковых модуля, но каждый из них работает со своим физическим диском.
Теоретически, скорость загрузки данных возрастет в два раза.
Далее, данные нужно обработать и сохранить на диск.
Зачем для этого держать всю программу в памяти? Оставляем один тред-демон, который занимается обсчетом данных и записью их на диск, а саму программу закрываем.

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

Either we are using English and then English terminology is obviously fine — or we are using Russian и тогда у нас есть потоки и процессы. Не надо умножать сущности без необходимости.

Так же тред может работать даже после завершения программы — это тред-демон (daemon-thread), особый тип тредов.

Где вы такое вычитали? В системах где потоки и процессы являются разными сущностями процесс — это контейнер для потоков. Процесс может завершить работу только с последним потоком. Иногда выход из главного потока убивает все остальные, иногда — процесс существует пока есть хотя бы один поток, но если поток жив, то жив и «охватывающий» его процесс.

Для высокой производительности как раз важно отличать их друг от друга. Даже Linux на самом деле вынужден учитывать особенности threads в планировщике, особенно для SMP, SMT и мультиядерной архитектуры.

Он там учитывает много чего: квоты на группы, доступ к памяти и т.д. и т.п.

Формально оформляя threads как процессы, в планировщике они вынуждены признать, что эти процессы особенные, например, у них у всех одно адресное пространство и нужно оптимизировать переключения задач так, чтобы поменьше переключаться между пространствами и не трешить TLB и прочие внутренние буферы процессора.

Процессы, нити и волокна в ОС Windows (стр. 1 из 2)

Реферат по теме:

1.Процессы, нити и волокна в ОС Windows

1. Процессы, нити и волокна в ОС Windows

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

– структура данных, содержащая всю информацию о процессе, в том числе список открытых дескрипторов различных системных ресурсов, уникальный идентификатор процесса, различную статистическую информацию и т.д.;

– адресное пространство – диапазон адресов виртуальной памяти, которым может пользоваться процесс;

– исполняемая программа и данные, проецируемые на виртуальное адресное пространство процесса.

Процессы инертны. Отвечают же за исполнение кода, содержащегося в адресном пространстве процесса, нити. Нить (thread) – некая сущность внутри процесса, получающая процессорное время для выполнения. В каждом процессе есть минимум одна нить. Эта первичная нить создается системой автоматически при создании процесса. Далее эта нить может породить другие нити, те в свою очередь новые и т.д. Таким образом, один процесс может владеть несколькими нитями, и тогда они одновременно исполняют код в адресном пространстве процесса. Каждая нить имеет:

– уникальный идентификатор нити;

– содержимое набора регистров процессора, отражающих состояние процессора;

– два стека, один из которых используется нитью при выполнении в режиме ядра, а другой – в пользовательском режиме;

– закрытую область памяти, называемую локальной памятью нити (thread local storage, TLS) и используемую подсистемами, run-time библиотеками и DLL.


Чтобы все нити работали, операционная система отводит каждой из них определенное процессорное время. Тем самым создается иллюзия одновременного выполнения нитей (разумеется, для многопроцессорных компьютеров возможен истинный параллелизм). В Windows реализована система вытесняющего планирования на основе приоритетов, в которой всегда выполняется нить с наибольшим приоритетом, готовая к выполнению. Выбранная для выполнения нить работает в течение некоторого периода, называемого квантом. Квант определяет, сколько времени будет выполняться нить, пока операционная система не прервет ее. По окончании кванта операционная система проверяет, готова ли к выполнению другая нить с таким же (или большим) уровнем приоритета. Если таких нитей не оказалось, текущей нити выделяется еще один квант. Однако нить может не полностью использовать свой квант. Как только другая нить с более высоким приоритетом готова к выполнению, текущая нить вытесняется, даже если ее квант еще не истек.

Квант не измеряется в каких бы то ни было единицах времени, а выражается целым числом. Для каждой нити хранится текущее значение ее кванта. Когда нити выделяется квант процессорного времени, это значит, что ее квант устанавливается в начальное значение. Оно зависит от операционной системы. Например, для Win2000 Professional начальное значение кванта равно 6, а для Win2000 Server – 36.

Всякий раз, когда возникает прерывание от таймера, из кванта нити вычитается 3, и так до тех пор, пока он не достигнет нуля. Частота срабатывания таймера зависит от аппаратной платформы. Например, для большинства однопроцессорных x86 систем он составляет 10 мс, а на большинстве многопроцессорных x86 систем – 15 мс.

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

Планирование в Windows осуществляется на уровне нитей, а не процессов. Это кажется понятным, так как сами процессы не выполняются, а лишь предоставляют ресурсы и контекст для выполнения нитей. Поэтому при планировании нитей, система не обращает внимания на то, какому процессу они принадлежат. Например, если процесс А имеет 10 готовых к выполнению нитей, а процесс Б – две, и все 12 нитей имеют одинаковый приоритет, каждая из них получит 1/12 процессорного времени.

В Windows существует 32 уровня приоритета, от 0 до 31. Они группируются так: 31–16 – уровни реального времени; 15–1 – динамические уровни; 0 – системный уровень, зарезервированный для процесса обнуления страниц (zero-page thread).

Приоритет каждой нити (базовый приоритет нити) складывается из приоритета ее процесса и относительного приоритета самой нити. Есть семь относительных приоритетов нитей:

Normal: такой же как и у процесса;

Above normal: +1 к приоритету процесса;

Timecritical: устанавливает базовый приоритет потока для Realtime класса в 31,

для остальных классов – в 15;

Idle: устанавливает базовый приоритет потока для Realtime класса в 16,

для остальных классов – в 1.

Если операционная система выполняется на машине, где установлено более одного процессора, то по умолчанию, нить выполняется на любом доступном процессоре. Однако в некоторых случаях, набор процессоров, на которых нить может работать, может быть ограничен. Это явление называется привязкой к процессорам (processoraffinity). Можно изменить привязку к процессорам программно, через Win32‑функции планирования.

Волокна поддерживаются в WIN32 API, начиная с Windows2000. Под волокном понимается упрощенная нить, выполнение которой планируется самим приложением, а не планировщиком процессорного времени ОС. Планирование волокон может осуществляться только путем переключения на них только из других волокон. Волокна выполняются в контексте нитей, в которых планируется их применение, и допускают полную их идентификацию с нитями. В каждой нити может быть запланировано несколько волокон. Для каждого волокна создается собственный стек, в котором хранится информация о состоянии волокна.

2. Создание процессов

Создание Win32 процесса осуществляется вызовом одной из таких функций, как CreateProcess , CreateProcessAsUser (для WinNT/2000/Vista) и CreateProcessWithLogonW (начиная с Win2000) и происходит в несколько этапов:

– Открывается файл образа (EXE), который будет выполняться в процессе. Если исполняемый файл не является Win32 приложением, то ищется образ поддержки (support image) для запуска этой программы. Например, если исполняется файл с расширением.bat, запускается cmd.exe и т.п.

В WinNT/2000/Vista для отладки программ реализовано следующее. CreateProcess , найдя исполняемый Win32 файл, ищет в SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Option раздел с именем и расширением запускаемого файла, затем ищет в нем параметр Debugger, и если строка не пуста, запускает то, что в ней написано вместо данной программы.

– Создается объект Win32 «процесс».

– Создается первичная нить (стек, контекст и объект «нить»).

– Подсистема Win32 уведомляется о создании нового процесса и нити.

– Начинается выполнение первичной нити.

– В контексте нового процесса и потока инициализируется адресное пространство (например, загружаются требуемые DLL) и начинается выполнение программы.

3. Завершение процессов

Процесс завершается если:

– Входная функция первичной нити возвратила управление.

– Одна из нитей процесса вызвала функцию ExitProcess .

– Нить другого процесса вызвала функцию TerminateProcess .

Когда процесс завершается, все User- и GDI‑объекты, созданные процессом, уничтожаются, объекты ядра закрываются (если их не использует другой процесс), адресное пространство процесса уничтожается.

Пример: Программа создает процесс «Калькулятор».

int main (int argc, char* argv[])


if (! CreateProcess(NULL, «c:\windows\calc.exe», NULL, NULL, FALSE,

0, NULL, NULL, &si, &pi))

// Close process and thread handles.

4. Создание нитей

Первичная нить создается автоматически при создании процесса. Остальные нити создаются функциями CreateThread и CreateRemoteThread (только в WinNT/2000/XP/Vista).

5. Завершение нитей

Нить завершается если

– Функция нити возвращает управление.

– Нить самоуничтожается, вызвав ExitThread .

– Другая нить данного или стороннего процесса вызывает TerminateThread .

– Завершается процесс, содержащий данную нить.

6. Создание волокон

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

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

#define _WIN32_WINNT 0x0400

void WINAPI Func (void *)

printf («Fiber Number % d\n», Counter % 4);

Процессы и нити

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

Процесс создается при запуске программы (EXE-файла). Одновременно создается одна нить процесса (должен же кто-то работать!). Создание процесса выполняется с помощью API-функции CreateProcess. Основными параметрами при вызове этой функции являются следующие.

· Имя файла запускаемой программы.

· Командная строка, передаваемая процессу при запуске.

· Атрибуты защиты для создаваемых процесса и нити. И процесс, и нить являются объектами ядра Windows и в этом качестве могут быть защищены от несанкционированного доступа (например, от попыток других процессов вмешаться в работу данного процесса).

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

· Блок среды процесса.

· Текущий каталог процесса.

· Параметры первого окна, которое будет открыто при запуске процесса.

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

Если процесс успешно создан, функция CreateProcess возвращает ненулевое значение.

Класс приоритета процесса используется при определении приоритетов его нитей. Подробнее об этом в п. 4.5.3.

Хэндл объекта ядра Windows (в данном случае процесса или нити) позволяет выполнять различные операции с этим объектом. Подробнее о хэндлах и идентификаторах см. п. 4.5.4.

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


· атрибуты защиты для создаваемой нити;

· размер стека нити;

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

· параметр-указатель, позволяющий передать нити при запуске некоторое значение в качестве аргумента;

· флаг создания нити в приостановленном состоянии;

· указатель на переменную, в которой функция должна возвратить идентификатор созданной нити.

Возвращаемым значением функции CreateThread является хэндл созданной нити либо NULL, если создать нить не удалось.

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

Для завершения работы нити используется вызов функции ExitThread. Для завершения работы всего процесса любая из его нитей может вызвать функцию ExitProcess. Единственным параметром каждой из этих функций является код завершения нити или процесса.

Завершение процесса приводит к освобождению всех ресурсов, которыми владел процесс: памяти, открытых файлов и т.п.

При завершении процесса завершаются все его нити. И наоборот, при завершении последней нити процесса завершается и сам процесс.

Не слишком широко известно, что нить не является самой мелкой единицей организации вычислений. На самом деле Windows позволяет создать внутри нити несколько волокон (fiber), которые в обычной терминологии могут быть описаны как сопрограммы или как задачи с невытесняющей диспетчеризацией, работающие в рамках одной и той же задачи с вытесняющей диспетчеризацией. Переключение волокон выполняется только явно, с помощью функции SwitchToFiber. Об использовании сопрограмм см. /Ошибка! Источник ссылки не найден./.

Планировщик Windows

Задачей планировщика является выбор очередной нити для выполнения. Планировщик вызывается в трех случаях:

· если истекает квант времени, выделенный текущей нити;

· если текущая нить вызвала блокирующую функцию (например, WaitForMultipleObjects или ReadFile) и перешла в состояние ожидания;

· если нить с более высоким приоритетом пробудилась от ожидания или была только что запущена.

Для выбора нити используется алгоритм приоритетной очереди. Для каждого уровня приоритета система ведет очередь активных нитей (т.е. нитей, находящихся в состоянии готовности). Для выполнения выбирается очередная нить из непустой очереди с самым высоким приоритетом.

Очереди активных нитей пополняются за счет нитей, проснувшихся после состояния ожидания и нитей, вытесненных планировщиком. Как правило, нить помещается в конец очереди. Исключение делается для нити, вытесненной до истечения ее кванта времени более приоритетной нитью. Такая «обиженная» нить ставится в голову очереди.

Значение кванта времени для серверных установок Windows равно обычно 120 мс, для рабочих станций – 20 мс.

Как вы думаете, почему для серверов квант времени больше?

Теперь подробнее о приоритетах. Хотя общая схема их назначения одинакова для всех версий Windows, детали могут разниться. Дальнейшее изложение ориентировано на Windows NT 4.0.

Все уровни приоритета нитей пронумерованы от 0 (самый низкий приоритет) до 31 (самый высокий). Уровни от 16 до 31 называются приоритетами реального времени, они предназначены для выполнения критичных по времени системных операций. Только сама система или пользователь с правами администратора могут использовать приоритеты из этой группы. Уровни от 0 до 15 называются динамическими приоритетами.

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

· Realtime (базовый приоритет 24) – высший класс приоритета, допустимый только для системных процессов, занимающих процессор на очень короткое время;

· High (базовый приоритет 13) – класс высокоприоритетных процессов;

· Normal (базовый приоритет 8) – обычный класс приоритета, к которому относится большая часть запускаемых прикладных процессов;

· Idle (базовый приоритет 4) – низший (буквально – «холостой» или «простаивающий») класс приоритета, характерный для экранных заставок, мониторов производительности и других программ, которые не должны мешать жить более важным программам.

Собственно приоритет связывается не с процессом, а с каждой его нитью. Приоритет нити определяется базовым приоритетом процесса, к которому прибавляется относительный приоритет нити – величина от –2 до +2. Относительный приоритет назначается нити при ее создании и может при необходимости изменяться. Имеется также возможность назначить нити критический приоритет (31 для процессов реального времени, 15 для остальных) или холостой приоритет (16 для процессов реального времени, 0 для остальных).

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

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

· Когда заблокированная нить дождалась нужного ей события, к приоритету нити прибавляется величина, зависящая от причины ожидания. Эта прибавка может достигать 6 единиц (но приоритет не должен превысить 15), если нить разблокирована вследствие нажатия клавиши или кнопки мыши. Таким способом система стремится уменьшить время реакции на действия пользователя. Всякий раз, когда нить полностью использует свой квант времени, прибавка уменьшается на 1, пока приоритет нити не вернется к своему заданному значению.

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

· Если планировщик обнаруживает, что некоторая нить пребывает в очереди более 3 с, то он повышает ее приоритет аж до 15 и удваивает ее квант. Но эта благотворительность разовая: когда Золушка-нить израсходует увеличенный квант или заблокируется, ее приоритет и квант возвращаются к прежним значениям. Смысл акции понятен: система пытается обеспечить хоть какое-то продвижение даже для низкоприоритетных нитей.

Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL