Что такое поток в программировании

Поток (программирование)

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

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

Поддержка потоков включена в большинство языков программирования и едва ли не во все современные (на 2008 год) операционные системы.

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

Возможность перенаправления потоков позволяет связывать различные программы, и придаёт системе гибкость, являющуюся частью философии Unix.

Поток данных в программировании

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

Поток данных в операционных системах

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

См. также

* Стандартные потоки
* Враппер

Wikimedia Foundation . 2010 .

Смотреть что такое «Поток (программирование)» в других словарях:

Поток выполнения — Для термина «Поток» см. другие значения. Процесс с двумя потоками выполнения на одном процессоре Поток выполнения (анг … Википедия

Поток минимальной стоимости — Задача о потоке минимальной стоимости состоит в нахождении самого дешёвого способа передачи определённого количества потока через транспортную сеть. Содержание 1 Определения 2 Отношение к другим задачам … Википедия

Поток POSIX — POSIX Threads стандарт потоков (нитей) выполнения, определяющий API для создания и управления ими. Библиотеки, реализующие этот стандарт (и функции этого стандарта), обычно называются Pthreads (функции имеют приставку «pthread »). Хотя наиболее… … Википедия

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

Класс (программирование) — У этого термина существуют и другие значения, см. Класс. Класс в программировании набор методов и функций. Другие абстрактные типы данных  метаклассы, интерфейсы, структуры, перечисления  характеризуются какими то своими, другими… … Википедия

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

Событийно-ориентированное программирование — Парадигмы программирования Агентно ориентированная Компонентно ориентированная Конкатенативная Декларативная (контрастирует с Императивной) Ограничениями Функциональная Потоком данных Таблично ориентированная (электронные таблицы) Реактивная … Википедия

Реактивное программирование — Парадигмы программирования Агентно ориентированная Компонентно ориентированная Конкатенативная Декларативная (контрастирует с Императивной) Ограничениями Функциональная Потоком данных Таблично ориентированная (электронные таблицы) Реактивная … Википедия

Грамотное программирование — Стиль этой статьи неэнциклопедичен или нарушает нормы русского языка. Статью следует исправить согласно стилистическим правилам Википедии … Википедия

Парное программирование — техника программирования, при которой весь исходный код создаётся парами людей, программирующих одну задачу, сидя за одним рабочим местом. Один программист управляет компьютером и, в основном, думает над кодированием в деталях. Другой программист … Википедия

[C++] часть 1: многопоточность, конкурентность и параллелизм: ОСНОВЫ

Простое руководство по изучению многопоточности, конкурентности и параллелизма в C++

Вначале, когда ещё только состоялось моё знакомство с многопоточностью в C++, многое было мне непонятным и сбивало с толку. Сложность программы расцветала буйным цветом (именно так: подобно прекрасному цветку), конкурентность и параллелизм с их недетерминированным поведением меня просто убивали, и всё было как в тумане. Так что мне легко понять всех приступающих к изучению этих понятий. Спешу избавить вас от мучений и предлагаю вашему вниманию это простое руководство по изучению конкурентности, параллелизма и многопоточности в C++ (в конце данной статьи расписан план, в соответствии с которым мы будем двигаться дальше).

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

1. Что такое поток?

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

2. Что такое конкурентность/параллелизм

Планировщик распределяет процессорное время между разными потоками. Это называется аппаратным параллелизмом или аппаратной конкурентностью (пока что считаем здесь параллелизм и конкурентность синонимами): когда несколько потоков выполняются на разных ядрах параллельно, причём каждый занимается конкретной задачей программы.
Примечание: чтобы определить количество задач, которые реально можно выполнять в многопоточном режиме на том или ином компьютере, используется функция std::thread::hardware_concurrency() . Если число потоков будет превышать этот лимит, может начаться настоящая чехарда с переключением задач (когда слишком частые переключения между задачами — много раз в секунду — создают лишь иллюзию многопоточности).

3. Основные операции с потоками с помощью std::thread

  • Заголовочный файл| #include <thread>
  • Запуск потока| std::thread t(callable_object, arg1, arg2, ..)
    Создаёт новый поток выполнения, ассоциируемый с t, который вызывает callable_object(arg1, arg2) . Вызываемый объект (т.е. указатель функции, лямбда-выражение, экземпляр класса с вызовом функции operator ) немедленно выполняется новым потоком с (выборочно) передаваемыми аргументами. Они копируются по умолчанию. Если хотите передать по ссылке, придётся использовать метод warp к аргументу с помощью std::ref(arg) . Не забывайте: если хотите передать unique_ptr, то должны переместить его ( std::move(my_pointer) ), так как его нельзя копировать.
  • Жизненный цикл потока| t.join() и t.detach()
    Если основной поток завершает выполнение, все второстепенные сразу останавливаются без возможности восстановления. Чтобы этого не допустить, у родительского потока имеются два варианта для каждого порождённого:
    → Блокирует и ждёт завершения порождённого потока, вызывая на нём метод join .
    → Прямо объявляет, что порождённый поток может продолжить выполнение даже после завершения родительского, используя метод detach .
  • Запомните: объект потока можно перенести, но нельзя копировать.

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

4. Зачем нужна синхронизация?

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

  • Память — дом с привидениями
    Память никогда больше не будет обычным хранилищем данных — теперь это обитель привидений. Представьте: поток смотрит Netflix, уютно устроившись перед Smart TV, и тут вдруг экран мигает и выключается. В панике поток набирает 112, а в ответ… «Доставка пиццы, спасибо, что позвонили». Что происходит? А то, что в доме полно привидений (где в роли привидений другие потоки): они все в одной комнате и взаимодействуют с одними и теми же объектами (это называется гонка данных), но друг для друга они привидения.

Поток должен объявить, что он использует. А затем, прежде чем трогать этот объект, проверить, не использует ли его кто-то ещё. Зелёный поток смотрит ТВ? Значит, никто не должен трогать ТВ (другие могут рядышком сесть и посмотреть, если что). Это можно сделать с помощью мьютекса.

  • Нужны атомарные операции!
    Большинство операций неатомарные. Если операция неатомарная, можно увидеть её промежуточное состояние, так как она не является неделимой. Например: запись 64 битов, 32 бита за один раз. Во время этой операции другой поток может увидеть 32 старых бита и 32 новых, получая совершенно неверный результат. По этой причине результаты таких операций должны казаться атомарными, даже если они такими не являются.
    Примечание: даже инкремент не является атомарной операцией: int tmp = a; a = tmp + 1;
    Самое простое решение здесь — использовать шаблон std::atomic , который разрешает атомарные операции разных типов.
  • Когерентность кеша и выполнение с изменением очерёдности
    Каждое ядро пытается сохранить результаты какой-то работы, помещая недавние значения в локальный кеш. Но несколько потоков выполняются на разных ядрах, и значения, хранящиеся в кеше, больше не могут быть валидными, так что рано или поздно кеш должен обновляться. В то же время изменения не видны другим, пока кеш не очищен. Чтобы распространить изменения и обеспечить корректную видимость памяти, нужны определённые механизмы.
    Кроме того, для повышения эффективности процессор и/или компилятор может поменять очерёдность выполнения команд. Это может привести к непредсказуемому поведению в параллельно выполняемой программе, в связи с чем необходимо гарантировать исполнение критически важных команд в первоначальном порядке.
    Эта работа выполняется примитивами синхронизации, предполагающими использование барьеров доступа к памяти (строки кода, которые не вычеркнуть какими-то операциями) для обеспечения согласованности и предотвращения изменения очерёдности выполнения (инструкции внутри барьеров памяти нельзя вытащить оттуда).

Пример кода

Обратимся к коду. Теперь вы сами можете проверить это недетерминированное поведение многопоточности.

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

Но что здесь происходит? После того как поток А оценивает «значение» как истинное, поток B меняет его. Теперь мы внутри блока if , даже если нарушены ограничения.

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

Доступ должен быть синхронизирован.

Заключение

Вы можете сказать: «Батюшки! Сколько всего намешано в этой статье!» Просто помните, что не надо пытаться понять всё и сразу, важно ухватить основные идеи.

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

C#. Понятие потока. Архитектура потоков в C#. Потоки с опорными хранилищами. Потоки с декораторами. Адаптеры потоков

Понятие потока. Архитектура потоков в C#. Потоки с опорными хранилищами. Потоки с декораторами. Адаптеры потоков

Содержание

  • 1. Что такое поток в программировании? Понятие потока
  • 2. Архитектура потоков в .NET. Категории потоков
  • 3. Потоки с опорными хранилищами. Обзор
  • 4. Потоки с декораторами. Обзор
  • 5. Адаптеры потоков. Назначение. Обзор

Поиск на других ресурсах:

1. Что такое поток в программировании? Понятие потока

В программировании поток (stream) — это логическое устройство, предусматривающее:

  • потребление (получение) информации. В этом случае определяют термин поток ввода;
  • выработка (передача) информации. В этом случае определяют термин поток вывода.

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

C#. Взаимодействие потока с различными типами физических устройств ввода/вывода (принтер, удаленный компьютер, файл)

Рисунок 1. Взаимодействие потока с различными типами физических устройств ввода/вывода (принтер, удаленный компьютер, файл)

2. Архитектура потоков в .NET. Категории потоков

В технологии .NET потоки делятся на две основные категории (рисунок 2):

  • потоки с опорными хранилищами;
  • потоки с декораторами.

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

  • файл;
  • память;
  • сеть;
  • изолированное хранилище.

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

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

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

C#. Архитектура потоков в .NET

Рисунок 2. Архитектура потоков в .NET

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

3. Потоки с опорными хранилищами. Обзор

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

  • FileStream — класс, обеспечивает поток для файла. Класс содержит разнообразные средства обработки файлов. Эти средства обеспечивают как синхронное и асинхронное чтение из файла, так и синхронную и асинхронную запись в файл;
  • IsolatedStorage — абстрактный класс, который служит базовым для классов, реализующих доступ к изолированному хранилищу для файлов;
  • MemoryStream — класс, предназначенный для обработки потоков, которые размещаются в памяти;
  • NetworkStream — класс, содержащий средства представления потока данных в сети.
4. Потоки с декораторами. Обзор

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

  • BufferedStream — класс, содержащий средства буферизации при чтении данных из потока и записи данных в поток. Чтение/запись данных осуществляется через буфер — участок памяти заданного размера;
  • DeflateStream — класс, обеспечивающий методы для сжатия и распаковки потоков данных. Класс использует Deflate-алгоритм сжатия без потерь;
  • GZipStream — класс, реализующий методы и свойства для сжатия/распаковки данных потока на основе спецификации формата данных GZip;
  • CryptoStream — класс, осуществляющий над потоком данных криптографические преобразования.

Потоки с декораторами выделены в отдельный раздел классов в архитектуре .NET. Такое представление дает следующие преимущества:

  • потоки с декораторами отделяют операции шифрования, сжатия и другие от операций, применяемых в потоках с опорными хранилищами;
  • использование потоков с декораторами освобождает потоки с опорными хранилищами от необходимости выполнения шифрования, сжатия и т.д.;
  • декорирование потоков не зависит от изменения интерфейса в программе;
  • потоки-декораторы можно подключать во время выполнения;
  • поддержка паттерна Декоратор дает возможность объединять декораторы в цепочки (например, шифрование + сжатие).
5. Адаптеры потоков. Назначение. Обзор

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

Что такое поток в программировании

  • Open with Desktop
  • View raw
  • Copy raw contents Copy raw contents

Copy raw contents

Copy raw contents

Потоки, однопоточность и многопоточность

Потоком выполнения, тредом (англ. a thread of execution , thread ) называют наименьший набор инструкций, который может быть независимо обработан диспетчером операционной системы (англ. scheduler ).

Однопоточным (англ. single-threaded ) называют язык программирования, который имеет лишь один поток выполнения, называемый основным потоком (англ. main thread ).

Примером однопоточного языка является JavaScript.

Наличие одного потока означает, что в один момент времени может исполняться только одна операция.

Если язык программирования использует несколько потоков выполнения, то его называют многопоточным (англ. multi-threaded ).

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

Примеры многопоточных языков: Java, Go, C#, Rust.

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

Использование нескольких потоков заставляет решать программистов проблемы параллелизма (concurrency issues), в том числе проблему блокировки потоков (dead-locking): только один поток может использовать определённый ресурс в один момент времени, другие потоки должны ожидать освобождения ресурса.

Операциями ввода-выводы (англ. input/output , I/O ) называют взаимодействие некоторого обработчика информации (компьютера, системы) с окружающим миром (человеком или другим компьютером).

Ввод (англ. input ) — любые данные или сигналы, которые получает система.

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

Например, ниже приведён пример создания текстового файла README.md , который содержит строку Notes и имеет кодировку Unicode .

Вывод (англ. output ) — любые данные или сигналы, которые система отправляет окружающему миру.

Например, отображение информации на экране или в консоли являются операциями вывода.

Ниже представлен пример вывода содержимого файла, который был создан в примере выше.

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

Блокирующие и неблокирующие операции ввода-вывода

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

Операции, которые приводят к блокировке основного потока называют блокирующими операциями ввода-выввода (англ. blocking I/O ). Обычно к ним относят трудоёмкие синхронные операции ввода-вывода.

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

О синхронном программировании

Синхронным программированием (англ. synchronous programming ) называют такое поведение языка программирования, при котором операции (инструкции) в некотором блоке кода выполняются последовательно (синхронно), то есть в порядке их указания в коде.

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

Синхронная операция ввода-вывода (англ. synchronous I/O , sync I/O ) — операция ввода-вывода, которая выполняется синхронно. Основной поток дожидается окончания выполнения такой операции и только потом запускает следующую операцию.

Большинство операций являются синхронными вне зависимости от языка программирования. Например, синхронными являются операции:

  • Арифметические, логические и строковые операции.
  • Операции присваивания = и сравнения <,>,= .
  • Вызов функций и методов () .
  • Вывод на консоль.

Конструкции if..else , switch , while , for , for..of также работают синхронно.

Например, код ниже на языке JavaScript выполнится синхронно.

Об асинхронном программировании

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

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

Асинхронная операция ввода-вывода (англ. asynchronous I/O , async I/O ) — операция ввода-вывода, выполнение которой не препятствует запуску следующей операции.

Какие операции делают асинхронными в синхронном языке программирования?

Такие операции, выполнение которых может занять продолжительное время, а именно:

  • Работа с файловой системой (англ. file system ). Например, запись в файл и чтение из файла.
  • Работа с сетью (англ. networking ), чаще всего это HTTP -запросы, но также могут использоваться TCP , UDP , вебсокеты (англ. websockets ).
  • Работа с базами данных.
  • Таймеры и другие планируемые задачи.

Как реализуется асинхронность?

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

Пример асинхронного кода

Ниже представлен пример асинхронной функции setTimeout , которая создаёт таймер и по истечению указанного времени вызывает функцию обратного вызова () => console.log(‘2’) .

Как можно заметить, setTimeout не припятствует вызову следующего console.log() . Код выполнился асинхронно (не последовательно).

О системе типов и типизации

В рамках изучения языков программирования существует понятие системы типов.

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

Например, для переменной числового типа могут быть доступны операции инкремента и возведения в степень: x++ , x^2 , а для переменной строкового типаоперации поиска и получения подстроки: str.find(/* . */) , str.substring(/* . */) .

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

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

Типизация и её виды

Язык программирования, использующий систему типов, называется типизированным языком (англ.typed language).

В более широком смысле слова под типизацией (англ typing) подразумевают классификацию по типам. Конкретно же в рамках изучения некоторого языка программирования под типизацией подразумевают то, каким образом система типов этого языка обрабатывает типы данных.

Типизированный язык программирования может иметь:

Например, JavaScript является типизированным языком и имеет динамическую, слабую, неявную типизацию.

Статическая и динамическая типизация

Типизированный язык в определённый момент времени производит проверку типа (англ. type checking).

Проверка типа может производиться во время компиляции (англ. compile time) или в режиме реального времени (англ. run-time), то есть по ходу выполнения программы.

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

Пример языков со статической типизацией: Java, C#.

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

Пример языков с динамической типизацией: Python, JavaScript.

Слабая и сильная типизация

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

Пример языка со слабой типизацией: JavaScript.

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

Пример языков с сильной типизацией: Java, Python.

Например, нельзя сложить число и массив.

Явная и неявная типизация

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

Пример языков с явной типизацией: C++, C#.

При неявной типизации эта задание типов производится автоматически компиляторами и интерпретаторами.

Пример языка с неявной типизацией: JavaScript.

Кроссплатформенность и нативность

Кроссплатформенностью программного обеспечения (англ. crossplatform software ) называют способность программного обеспечения работать на нескольких аппаратных платформах (поддерживаются разными типами процессоров) или операционных системах ( Windows / MacOS / Linux ).

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

Нативные приложения (англ. native application , native app ) пишут на нативном языке программирования (англ. native programming languare , то есть на «родном» (характерном для платформы) языке. Поэтому нативные приложения обычно отличаются очень хорошей совместимостью и производительностью, так как используют максимум из преимуществ своей целевой платформы.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *