Vvmebel.com

Новости с мира ПК
3 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Поток в программировании это

Поток в программировании это

Системное программирование

Главная страница

Младший специалист

Бакалавр

О сайте

Дополнительные материалы

Пройти тест

Лекция №13. Потоковый ввод-вывод в стандарте Cи

Под вводом-выводом в программировании понимается процесс обмена информацией между оперативной памятью и внешними устройствами: клавиатурой, дисплеем, магнитными накопителя­ми и т. п. Ввод — это занесение информации с внешних устройств в оперативную память, а вывод — вынос информации из опера­тивной памяти на внешние устройства. Такие устройства, как дис­плей и принтер, предназначены только для вывода; клавиатура — устройство ввода. Магнитные накопители (диски, ленты) исполь­зуются как для ввода, так и для вывода.

Основным понятием, связанным с информацией на внешних устройствах ЭВМ, является понятие файла. Всякая операция вво­да-вывода трактуется как операция обмена с файлами: ввод — это чтение из файла в оперативную память; вывод — запись инфор­мации из оперативной памяти в файл. Поэтому вопрос об органи­зации в языке программирования ввода-вывода сводится к вопро­су об организации работы с файлами.

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

Аналогом понятия внутреннего файла в языках Си/Си++ яв­ляется понятие потока. Поток — это байтовая последовательность, передаваемая в про­цессе ввода-вывода.

Поток должен быть связан с каким-либо внешним устройством или файлом на диске. В терминологии Си это звучит так; поток должен быть направлен на какое-то устройство или файл.

Основные отличия файлов в Си состоят в следующем: здесь отсутствует понятие типа файла и, следовательно, фиксирован­ной структуры записи файла. Любой файл рассматривается как байтовая последовательность:

Стрелочкой обозначен указатель файла, определяющий теку­щий байт файла. EOF является стандартной константой — призна­ком конца файла.

Существуют стандартные потоки и потоки, объявляемые в про­грамме. Последние обычно связываются с файлами на диске, со­здаваемыми программистом. Стандартные потоки назначаются и открываются системой автоматически. С началом работы любой программы открываются 5 стандартных потоков, из которых ос­новными являются следующие:

o stdin — поток стандартного ввода (обычно связан с клавиатурой);

o stdout — поток стандартного вывода (обычно связан с дисплеем);

o stderr — вывод сообщений об ошибках (связан с диспле­ем).

Кроме этого, открывается поток для стандартной печати и до­полнительный поток для последовательного порта.

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

Работа с файлами на диске. Работа с дисковым файлом начи­нается с объявления указателя на поток. Формат такого объяв­ления:

Слово file является стандартным именем структурного типа, объявленного в заголовочном файле stdio.h. В структуре file содержится информация, с помощью которой ведется работа с потоком, в частности: указатель на буфер, указатель (индикатор) текущей позиции в потоке и т.д.

Следующий шаг — открытие потока, которое производится с помощью стандартной функции fopen (). Эта функция возвраща­ет конкретное значение для указателя на поток и поэтому ее зна­чение присваивается объявленному ранее указателю. Соответству­ющий оператор имеет формат:

Имя_указателя=fореn (“имя_файла”, “режим_открытия”) ;

Параметры функции fopen () являются строками, которые мо­гут быть как константами, так и указателями на символьные мас­сивы. Например:

Здесь test .dat — это имя физического файла в текущем ката­логе диска, с которым теперь будет связан поток с указателем fр. Параметр режима r означает, что файл открыт для чтения. Что касается терминологии, то допустимо употреблять как выражение «открытие потока», так и выражение «открытие файла».

Существуют следующие режимы открытия потока и соответ­ствующие им параметры:

r открыть для чтения

w создать для записи

а открыть для добавления

r+ открыть для чтения и записи

w+ создать для чтения и записи

а+ открыть для добавления или

создать для чтения и записи

Поток может быть открыт либо для текстового, либо для дво­ичного (бинарного) режима обмена.

Понятие текстового файла: это последовательность символов, которая делится на строки специальными кодами — возврат ка­ретки (код 13) и перевод строки (код 10). Если файл открыт в текстовом режиме, то при чтении из такого файла комбинация символов «возврат каретки — перевод строки» преобразуется в один символ n — переход к новой строке.

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

Указанные выше параметры режимов открывают текстовые файлы. Если требуется указать на двоичный файл, то к параметру добавляется буква b. Например: rb, или « b », или r +b. В некоторых компиляторах текстовый режим обмена обозначается буквой t, т.е. записывается a+t или rt.

Если при открытии потока по какой-либо причине возникла ошибка, то функция fopen() возвращает значение константы null. Эта константа также определена в файле stdio.h. Ошибка может возникнуть из-за отсутствия открываемого файла на диске, нехватки места в динамической памяти и т.п. Поэтому желатель­но контролировать правильность прохождения процедуры откры­тия файла. Рекомендуется следующий способ открытия:

if (fp=fopen(«test.dat», «r»)==NULL)

В случае ошибки программа завершит выполнение с закрыти­ем всех ранее открытых файлов.

Закрытие потока (файла) осуществляет функция fclose(), прототип которой имеет вид:

int fclose(FILE *fptr);

Здесь fptr обозначает формальное имя указателя на закрыва­емый поток. Функция возвращает ноль, если операция закрытия прошла успешно. Другая величина означает ошибку.

Запись и чтение символов. Запись символов в поток произво­дится функцией putc() с прототипом

int putc (int ch, FILE *fptr);

Если операция прошла успешно, то возвращается записанный символ. В случае ошибки возвращается константа EOF.

Считывание символа из потока, открытого для чтения, произ­водится функцией gets () с прототипом

int gets (FILE *fptr);

Функция возвращает значение считываемого из файла сим­вола. Если достигнут конец файла, то возвращается значение EOF. Заметим, что это происходит лишь в результате чтения кода EOF.

Исторически сложилось так, что gets() возвращает значение типа int. To же можно сказать и про аргумент ch в описании функции puts(). Используется же в обоих случаях только млад­ший байт. Поэтому обмен при обращении может происходить и с переменными типа char.

Пример 1. Составим программу записи в файл символьной пос­ледовательности, вводимой с клавиатуры. Пусть признаком завер­шения ввода будет символ *.

//Запись символов в файл

puts(«Вводите символы. Признак конца — *»);

В результате на диске (в каталоге, определяемом системой) будет создан файл с именем test.dat, который заполнится вводимы­ми символами. Символ * в файл не запишется.

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

//Чтение символов из файла

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

Запись и чтение целых чисел. Запись целых чисел в поток без преобразования их в символьную форму производится функцией putw () с прототипом

Читать еще:  Понятие класса в программировании

int putw(int, FILE *fptr);

Если операция прошла успешно, то возвращается записанное число. В случае ошибки возвращается константа EOF.

Считывание целого числа из потока, открытого для чтения, производится функцией getw () с прототипом

int getw(FILE *fptr);

Функция возвращает значение считываемого из файла числа. Если прочитан конец файла, то возвращается значение EOF.

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

Программирование на C, C# и Java

Уроки программирования, алгоритмы, статьи, исходники, примеры программ и полезные советы

ОСТОРОЖНО МОШЕННИКИ! В последнее время в социальных сетях участились случаи предложения помощи в написании программ от лиц, прикрывающихся сайтом vscode.ru. Мы никогда не пишем первыми и не размещаем никакие материалы в посторонних группах ВК. Для связи с нами используйте исключительно эти контакты: vscoderu@yandex.ru, https://vk.com/vscode

Потоки в C# для начинающих: разбор, реализация, примеры

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

Что такое потоки в C#

Если говорить простым языком, то поток – это некая независимая последовательность инструкций для выполнения того или иного действия в программе. В одном конкретном потоке выполняется одна конкретная последовательность действий.
Совокупность таких потоков, выполняемых в программе параллельно называется многопоточностью программы.

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

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

Стандартно в проектах Visual Studio существует только один основной поток – в методе Main. Всё, что в нём выполняется – выполняется последовательно строка за строкой. Но при необходимости можно “распараллелить” выполняемые процессы при помощи потоков.

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

Например, если один работник будет собирать шкаф час, то вдвоём они могут управиться уже за полчаса. Однако не стоит переусердствовать в количестве работников (потоков). Математически, если нанять 4 работника, то шкаф соберется за 15 минут, если нанять 60 работников – за 1 минуту, а если нанять 3600, то вообще за секунду, но ведь на деле это неверно. Работники будут только мешать друг другу, толкаться, отнимать друг у друга детали, и процесс сборки шкафа может затянуться очень надолго.

Так же и с потоками. Чем больше потоков, тем выше вероятность, что они будут мешать друг другу выполнять свою работу. Например, если заставить работать огромное количество потоков с одними и теми же данными, потокам придётся выстраиваться в очередь для их обработки (например, если тем же 3600 рабочим дать какое-либо письменное задание, но предоставить им для этого дела всего одну ручку, то работникам, естественно, придётся становиться друг за другом в очередь за ручкой, чтобы после её получения выполнить поставленную задачу. Времени это займёт довольно много).

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

Язык C# имеет встроенную поддержку многопоточности, а среда .NET Framework предоставляет сразу несколько классов для работы с потоками, что в купе очень помогает гибко и правильно реализовывать и настраивать многопоточность в проектах.

В среде .NET Framework существует два типа потоков: основной и фоновый (вспомогательный). В целом отличие между ними одно – если первым завершится основной поток, то фоновые потоки в его процессе будут также принудительно остановлены, если же первым завершится фоновый поток, то это не повлияет на остановку основного потока – тот будет продолжать функционировать до тех пор, пока не выполнит всю работу и самостоятельно не остановится. Обычно при создании потока ему по-умолчанию присваивается основной тип. О том, как узнать, к какой разновидности относится тот или иной поток, как придать потоку нужный тип, что такое приоритеты и как их устанавливать, и как в целом работать с потоками в C# мы поговорим ниже.

Реализация потоков в C#

Как создавать потоки в C#

Перво-наперво для работы с потоками в C# необходимо подключить специальную директиву:

Урок №207. Потоки ввода и вывода

Обновл. 12 Янв 2020 |

Функционал потоков ввода/вывода не определён как часть языка C++, а предоставляется Стандартной библиотекой C++ (и, следовательно, находится в пространстве имён std). В предыдущих уроках мы подключали заголовочный файл библиотеки iostream и использовали объекты cin и cout для простого ввода/вывода данных. В этом уроке мы рассмотрим библиотеку iostream детальнее.

Библиотека iostream

При подключении заголовочного файла iostream, мы получаем доступ ко всей иерархии классов библиотеки iostream, отвечающих за функционал ввода/вывода данных (включая класс, который называется iostream). Иерархия этих классов выглядит примерно следующим образом:

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

Потоки в С++

Второе, что вы могли бы заметить — это частое использование слова «stream» (т.е. «поток»). По сути, ввод/вывод в C++ реализован с помощью потоков. Абстрактно, поток — это последовательность символов, к которой можно получить доступ. Со временем поток может производить или потреблять потенциально неограниченные объёмы данных.

Мы будем иметь дело с двумя типами потоков. Поток ввода (или ещё «входной поток») используется для хранения данных, полученных от источника данных: клавиатуры, файла, сети и т.д. Например, пользователь может нажать клавишу на клавиатуре в то время, когда программа не ожидает ввода. Вместо игнорирования нажатия клавиши, данные помещаются во входной поток, где затем ожидают ответа от программы.

И наоборот, поток вывода (или ещё «выходной поток») используется для хранения данных, предоставляемых конкретному потребителю данных: монитору, файлу, принтеру и т.д. При записи данных на устройство вывода, это устройство может быть не готовым принять данные немедленно — например, принтер все ещё может прогреваться, когда программа уже записывает данные в выходной поток. Таким образом, данные будут находиться в потоке вывода до тех пор, пока принтер не начнёт их использовать.

Читать еще:  Что такое константа в программировании

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

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

Ввод/вывод в C++

Хотя класс ios является дочерним классу ios_base , очень часто именно этот класс будет наиболее родительским классом, с которым вы будете работать/взаимодействовать напрямую. Класс ios определяет кучу разных вещей, которые являются общими для потоков ввода/вывода.

Класс istream используется для работы с входными потоками. Оператор извлечения >> используется для извлечения значений из потока. Это имеет смысл: когда пользователь нажимает на клавишу клавиатуры, код этой клавиши помещается во входной поток. Затем программа извлекает это значение из потока и использует его.

Класс ostream используется для работы с выходными потоками. Оператор вставки istream , ostream и iostream (соответственно). В большинстве случаев вы не будете работать с ними напрямую.

Стандартные потоки в C++

Стандартный поток — это предварительно подключенный поток, который предоставляется программе её окружением. C++ поставляется с 4-мя предварительно определёнными стандартными объектами потоков, которые вы можете использовать (первые три вы уже встречали):

cin — класс istream_withassign , связанный со стандартным вводом (обычно это клавиатура);

cout — класс ostream_withassign , связанный со стандартным выводом (обычно это монитор);

cerr — класс ostream_withassign , связанный со стандартной ошибкой (обычно это монитор), обеспечивающий небуферизованный вывод;

clog — класс ostream_withassign , связанный со стандартной ошибкой (обычно это монитор), обеспечивающий буферизованный вывод.

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

Пример на практике

Вот пример использования ввода/вывода данных со стандартными потоками:

Многопоточность в C

Что такое тема?
Поток — это отдельный поток последовательности внутри процесса. Поскольку потоки имеют некоторые свойства процессов, их иногда называют.

Каковы различия между процессом и потоком?
Потоки не являются независимыми друг от друга, как процессы, в результате потоки делятся с другими потоками своими разделами кода, данными и ресурсами ОС, такими как открытые файлы и сигналы. Но, как и процесс, поток имеет свой собственный программный счетчик (ПК), набор регистров и пространство стека.

Почему многопоточность?
Потоки — это популярный способ улучшить приложение с помощью параллелизма. Например, в браузере несколько вкладок могут быть разными потоками. MS word использует несколько потоков, один поток для форматирования текста, другой поток для обработки ввода и т. Д.
Потоки работают быстрее, чем процессы по следующим причинам:
1) Создание темы намного быстрее.
2) Переключение контекста между потоками происходит намного быстрее.
3) темы могут быть легко прекращены
4) Связь между потоками быстрее.

rmuhamma/OpSystems/Myos/threads.htm для получения более подробной информации.

Можем ли мы написать многопоточные программы на C?
В отличие от Java, многопоточность не поддерживается стандартом языка. POSIX Threads (или Pthreads) — это стандарт POSIX для потоков. Реализация pthread доступна с компилятором gcc.

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

#include
#include
#include //Header file for sleep(). man 3 sleep for details.
#include

// Обычная функция C, которая выполняется как поток
// когда его имя указано в pthread_create ()

void *myThreadFun( void *vargp)

printf ( «Printing GeeksQuiz from Thread n» );

printf ( «Before Threadn» );

printf ( «After Threadn» );

В main () мы объявляем переменную с именем thread_id, которая имеет тип pthread_t, который является целым числом, используемым для идентификации потока в системе. После объявления thread_id мы вызываем функцию pthread_create () для создания потока.
pthread_create () принимает 4 аргумента.
Первый аргумент — это указатель на thread_id, который устанавливается этой функцией.
Второй аргумент определяет атрибуты. Если значение равно NULL, то должны использоваться атрибуты по умолчанию.
Третий аргумент — это имя функции, которая должна быть выполнена для создаваемого потока.
Четвертый аргумент используется для передачи аргументов функции, myThreadFun.
Функция pthread_join () для потоков является эквивалентом wait () для процессов. Вызов pthread_join блокирует вызывающий поток, пока поток с идентификатором, равным первому аргументу, не завершится.

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

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

#include
#include
#include
#include

// Давайте создадим глобальную переменную, чтобы изменить ее в потоках

// Функция, выполняемая всеми потоками

void *myThreadFun( void *vargp)

// Сохраняем аргумент значения, переданный в этот поток

int *myid = ( int *)vargp;

// Давайте создадим статическую переменную для наблюдения за ее изменениями

static int s = 0;

// Изменить статические и глобальные переменные

// Распечатать аргумент, статические и глобальные переменные

printf ( «Thread ID: %d, Static: %d, Global: %dn» , *myid, ++s, ++g);

Библиотека Warp

Оригинал: Warp
Авторы: Kazu Yamamoto, Michael Snoyman, Andreas Voellmy
Дата публикации: 2013 г.
Перевод: Н.Ромоданов
Дата перевода: февраль 2014 г.

Warp является высокопроизводительной библиотекой для серверов HTTP, которая написана на языке Haskell, чисто функциональном языке программирования. На базе Warp реализованы Yesod — фреймворк веб-приложений и mighty — HTTP-сервер. Согласно нашему бенчмарку пропускной способности сервер mighty сравним по производительности с сервером nginx. В настоящей статье будет рассказано об архитектуре Warp и о том, как мы достигли высокой производительности. Библиотека Warp может работать на многих платформах, в том числе на Linux, вариантах BSD, Mac OS и Windows. Однако для простоты изложения мы в данной статье будем говорить только о системе Linux.

Сетевое программирование на языке Haskell

Некоторые считают, что функциональные языки программирования медленны или непрактичны. Однако, насколько нам известно, в языке Haskell предлагается почти идеальный подход для сетевого программирования. Это объясняется тем, что в компиляторе Glasgow Haskell Compiler (GHC), который является флагманом для языка Haskell, поддерживаются легковесные и надежные пользовательские потоки (иногда называемые зелеными потоками). В этом разделе мы кратко рассмотрим некоторые известные подходы сетевого программирования на серверной стороне и сравним их с сетевым программированием в языке Haskell. Мы покажем, что в языке Haskell предлагается такое сочетание приемов программирования и показателей производительности, которые недоступны при других подходах: удобные абстракции языка Haskell позволяют программистам писать четкий и простой код, а сложный компилятор GHC и многоядерная система времени выполнения создают программы для многоядерной среды, которые выполняются почти также, как современные сетевые программы, создаваемые вручную.

Нативные потоки

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

Читать еще:  Что такое многопоточность в программировании

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

Рис.11.1: Нативные потоки

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

Архитектура с событийным управлением

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

Рис.11.2: Архитектура с событийным управлением

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

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

По одному процессу на каждое ядро

Многим пришла в идея для того, чтобы использовать N ядер, создать N процессов, управляемых событиями (рис. 11.3). Каждый процесс называется рабочим процессом (worker). Рабочие процессы должны совместно использовать порт сервиса (прим.пер.: разделять между собой использование порта сервиса). Совместное использование порта можно достичь с помощью технологии prefork.

В традиционном программировании процесса после того, как создается соединение, происходит разветвление процесса. В противоположность этому, разветвление процесса при использовании технологии prefork происходить еще создание новых соединений. Несмотря на одинаковое название, этот метод не следует путать с режимом prefork в сервере Apache.

Рис.11.3: По одному процессу на каждое ядро

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

Пользовательские потоки

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

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

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

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

В языке Haskell, большая часть вычисления является недеструктивными. Это означает, что почти все функции потокобезопасные. Компилятор GHC использует выделение данных в качестве места и момента, безопасного для переключения контекста пользовательских потоков. Из-за функционального стиля программирования, новые данные создаются часто и известно, что такое выделение данных происходит достаточно регулярно с тем, чтобы его было достаточно для переключения контекста .

Хотя в прошлом в некоторых языках были пользовательские потоки, в настоящее время эти языки не применяются, т.к. потоки в них не были ни легковесными, ни надежными. Обратите внимание, что в некоторых языках предлагались сопрограммы уровня библиотек, но это не были потоки с вытесняющими приоритетами. Отметим также, что в Erlang и в Go предлагаются легковесные процессы и легковесные go-процедуры, соответственно.

На момент написания статьи, в mighty для того, чтобы с целью задейсвовать больше количество ядер, использовалась технология prefork разветвления процессов. (В Warp нет такой функциональной возможности). На рис.11.4 проиллюстрировано такое распределение в контексте веб-сервера с технологией prefork, записанной на языке Haskell, в котором каждое подключение браузера обрабатывается в отдельном пользовательском потоке, а каждый нативный поток в процессе, работающем в ядре процессора, выполняет работу для нескольких подключений.

Рис.11.4: Пользовательские потоки с одним процессом на каждое ядро

Мы обнаружили, что компонент менеджера ввода/вывода системы времени выполнения компилятора GHC сам имеет по производительности узкие места. Чтобы решить эту проблему, мы разработали менеджер параллельного ввода/вывода (parallel I/O manager), в котором для каждого ядра используются таблицы регистрации событий и мониторы событий, что значительно улучшает многоядерное масштабирование. Программа на языке Haskell с менеджером параллельного ввода/вывода выполняется как один процесс, а многочисленные менеджеры ввода/вывода для того, чтобы использовать много ядер, выполняются как нативные потоки (рис. 11.5). Любой пользовательский поток может быть выполнен на любом из ядер.

Рис.11.5: Пользовательские потоки в одном процессе

Компилятор GHC версии 7.8, в который добавлен менеджер параллельного ввода/вывода, будет выпущен осенью 2013 года. При наличии компилятора GHC версии 7.8 библиотека Warp сама может использовать эту архитектуру без каких-либо модификаций, а в сервере mighty не потребуется пользоваться технологией prefork.

Ссылка на основную публикацию
Adblock
detector
×
×