Как сделать поток в с

Обновлено: 08.07.2024

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

В семействах ОС Windows - каждая программа запускает один процесс выполнения, в котором находится как минимум один поток (нить). В процессе может находиться множество потоков, между которыми делится процессорное время. Один процесс не может напрямую обратиться к памяти другого процесса, а потоки же разделяют одно адресное пространство одного процесса. То есть в Windows - процесс это совокупность потоков.

В Linux же немного по-другому. Сущность процесса такая же, как и в Windows - это исполняемая программа со своими данными. Но вот поток в Linux является отдельным процессом (можно встретить название как "легковесный процесс", LWP). Различие такое же - процесс отдельная программа со своей памятью, не может напрямую обратиться к памяти другого процесса, а вот поток, хоть и отдельный процесс, имеет доступ к памяти процесса-родителя [2]. LWP процессы создаются с помощью системного вызова clone() с указанием определенных флагов.

Но также имеется такая вещь, которая называется "POSIX Threads" - библиотечка стандарта POSIX, которая организует потоки (они же нити) внутри процесса. Т.е тут уже распараллеливание происходит в рамках одного процесса.

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

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

Я рассмотрю два варианта "распараллеливания" программы - создания потока/нити с помощью функций из pthread.h (POSIX Threads), либо создание отдельного процесса с помощью функции fork().

Для вывода данных используеся оператор . Этот опрератор определен для всех встроенных типов C++ и некоторых классов, входящих в стандартную библиотеку. Для вывода перевода строки можно использовать специальный объект endl .

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

Также оператор можно использовать и со своими классами, определив для них перегруженный оператор. Например, для структуры Book перегруженный оператор может выглядеть так:

Некоторые методы класса ostream:

  • put(char c) - записать символ с в поток
  • write(const char* s, streamsize n) - записать первые n элементов массива s в поток (streamsize представляет целое число со знаком, например, int)
  • flush() - записать значение из буфера
  • close() - закрытие потока

Потоки ввода (istream)

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

Оператор >> также можно определить для своих классов:

Некоторые методы класса istream:

  • get() - считать следующий символ
  • get(char *buf, streamsize n) - считать максимум n-1 символ и поместить в массив buf
  • get(char *buf, streamsize n, char delim) - считывание символов до символа-разделителя delim (разделитель не считывается и остается в потоке)
  • getline(char *buf, streamsize n)
  • getline(char *buf, streamsize n, char delim)
  • peek() - считывает следующий символ, но оставляет его в потоке
  • ignore(streamsize n = 1, int delim = EOF) - извлекает символы из потока до тех пор, пока их число меньше n или пока не встретился символ delim
  • putback(char c) - добавляет символ с в текущую позицию потока
  • unget() - возвращает последний считанный символ в поток

Форматирование

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

Основные манипуляторы ввода/вывода:

  • boolalpha - стороковое представление логических значений
  • noboolalpha - числовое представление логических значений
  • showbase - включает вывод 0 перед восьмеричными и 0x перед шестнадцатеричными числами
  • noshowbase - выключает вывод 0 и 0x
  • dec - вывод чисел в десятичной системе счисления
  • oct - в восьмеричной
  • hex - в шестнадцатеричной
  • uppercase - заглавные буквы в записи шестнадцатеричных чисел и чисел с плавающей запятой в научной записи
  • nouppercase - строчные буквы в записи чисел
  • skipws - пропуск символов-разделителей ( ' ', '\t', '\n', и т.п. )
  • noskipws - выключение пропуска разделителей
  • setw(int n) - определяет минимальное количество символов, которые выведутся следующей операцией вывода
  • setfill(char c) - символ-заполнитель
  • left - выравнивание поля по левому краю
  • right - выравнивание поля по правому краю
  • internal - выравнивание поля по ширине
  • scientific - научная запись для чисел с плавающей запятой
  • fixed - фиксированная точность для чисел с плавающей запятой
  • setprecision - точность вывода чисел (по умолчанию равна 6)
  • endl - запись \n и очистка буфера
  • ends - запись \0
  • flush - очистка буфера потока
  • ws - прочитать и проигнорировать символы-разделители

Сотояние потока

Каждый поток istream или ostream имеет связанное с ним состояние.

Методы проверки состояния:

  • good() - можно выполнить следующую операцию
  • eof() - конец потока
  • fail() - следующая операция не выполнится
  • bad() - поток испорчен

Стандартные потоки - iostream

Для реализации стандартного ввода/вывода в библиотеку C++ включен заголовочный файл iostream , содержащий следующие предопределенные объекты потоков:

  • cin - стандартный поток ввода (соответствует потоку C stdin)
  • cout - стандартный поток вывода (соответствует stdout)
  • cerr - стандартный поток вывода ошибок (соответствует stderr)
  • clog - стандартный поток вывода журнала (соответствует stderr)

Файловые потоки - fstream

Файловые потоки расположены в заголовочном файле . ifstream - поток ввода, ofstream - поток вывода.

Имя файла передается потоку либо в конструкторе, либо через вызов метода open .

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

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

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

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

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


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

void *myThreadFun( void *vargp)

printf ( "Printing GeeksQuiz from Thread \n" );

printf ( "Before Thread\n" );

printf ( "After Thread\n" );

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

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

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


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


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

void *myThreadFun( void *vargp)

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

int *myid = ( int *)vargp;

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

Поточный ввод-вывод в C++ выполняется с помощью функций сторонних библиотек. В С++, как и в С, нет встроенных в язык средств ввода-вывода.

В С для этих целей используется библиотека stdio.h .
В С++ разработана новая библиотека ввода-вывода iostream , использующая концепцию объектно-ориентированного программирования:

Библиотека iostream определяет три стандартных потока:

Для их использования в Microsoft Visual Studio необходимо прописать строку:

Для выполнения операций ввода-вывода переопределены две операции поразрядного сдвига:

Возможно многократное назначение потоков:
cout

int n;
char j;
cin >> n >> j;
cout "Значение n равно" n "j prog">cin >> идентификатор;

При этом из входного потока читается последовательность символов до пробела, затем эта последовательность преобразуется к типу идентификатора, и получаемое значение помещается в идентификатор:

Возможно многократное назначение потоков:
cin >> переменная1 >> переменная2 >>. >> переменнаяn;

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

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

Для ввода текста до символа перевода строки используется манипулятор потока getline() :

Манипуляторы потока

Функцию - манипулятор потока можно включать в операции помещения в поток и извлечения из потока ( >).

В С++ имеется ряд манипуляторов. Рассмотрим основные:

Пример Программа ввода-вывода значения переменной в C++

Та же программа, написанная на языке Си

Пример Использование форматированного вывода

Еще один пример использования форматированного вывода: для t∈[0;3] с шагом 0,5 вычислить значение y=cos(t).

Читайте также: