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

Процесс против потока

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

Теперь приступим к программированию POSIX… ..

POSIX Threads или Pthreads

Потоки POSIX или чаще называемые Pthreads определяют интерфейс прикладного программирования (API) для многопоточного программирования на основе UNIX. В отличие от C или Java, Pthreads не является языком программирования, а скорее представляет собой библиотеку, которая может быть связана с программами на C. За исключением Pthreads, существуют некоторые другие спецификации для многопоточного программирования, такие как потоки Java, потоки Windows, потоки Solaris и т. Д. Однако в этой статье давайте посмотрим на Pthreads, и как только мы закончим, не составит труда научиться программа с другим API потока, о котором я упоминал ранее.

Почему именно Pthreads?

  • Легковесность. Как правило, затраты на создание процесса и управление им выше, чем у потока, и поэтому потоки могут быть созданы с меньшими накладными расходами ОС. Кроме того, потокам требуется меньше системных ресурсов, чем процессам.
  • Эффективная связь / обмен данными - по сравнению с использованием библиотек MPI (интерфейса передачи сообщений) для связи на узле использование потоков Pthread может стать огромным преимуществом по сравнению с достижением лучшей производительности. Библиотеки MPI обычно реализуют взаимодействие задач на узле через разделяемую память, что включает в себя как минимум одну операцию копирования памяти (процесс для обработки). В то время как для Pthreads не требуется промежуточного копирования памяти, поскольку потоки совместно используют одно и то же адресное пространство в одном процессе. Следовательно, передача данных отсутствует и может быть столь же эффективной, как простая передача указателя.

API Pthreads

API Pthreads можно неофициально разделить на четыре основные группы.

  1. Управление потоками: обрабатывает создание, завершение и присоединение потоков и т. д.
  2. Мьютексы: касается синхронизации, называемой «мьютексом», аббревиатурой взаимного исключения. Функции мьютексов обеспечивают создание, уничтожение, блокировку и разблокировку мьютексов.
  3. Переменные условия: адрес обмена данными между потоками, использующими мьютекс. Это определяет функции для создания, уничтожения, ожидания и сигнализации на основе указанных значений переменных.
  4. Синхронизация: управляйте блокировками и барьерами чтения / записи.

В этой статье давайте узнаем, как создавать и завершать потоки с помощью Pthreads API.

Исполнение

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

gcc -g -Wall -o pth_name pth_name.c -lpthread

Управление потоками

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

Первоначально ваша программа main () состоит из одного потока по умолчанию, но все остальные потоки должны быть явно созданы программистом. Итак, чтобы создать новый поток, мы можем использовать функцию pthread_create в Pthreads API. Синтаксис pthread_create:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
    void *(*start_routine)(void*), void *arg);

Функция pthread_create имеет четыре аргумента:

  • поток: непрозрачный уникальный идентификатор нового потока, возвращаемый подпрограммой.
  • attr: непрозрачный объект атрибута, который может использоваться для установки атрибутов потока. Вы можете указать объект атрибутов потока или NULL для значений по умолчанию.
  • start_routine: процедура C, которую поток будет выполнять после создания.
  • arg: единственный аргумент, который можно передать в start_routine. Он должен передаваться по ссылке как приведение указателя типа void. Если аргумент не передается, можно использовать NULL.

Однако есть несколько способов завершения потока. Обычно поток завершается после завершения своей работы или при вызове подпрограммы pthreads_exit. Кроме того, поток отменяется другим потоком с помощью процедуры pthread_cancel. За исключением этого, весь процесс можно прервать, вызвав либо exec (), либо exit (). Однако проблема возникает, если main () завершается раньше, чем потоки, которые она порождает, и если вы не вызываете pthread_exit () явно. В этом сценарии он завершит все потоки, которые он создал, поскольку main () выполнен и больше не существует. Но если вы вызываете pthread_exit () явно, тогда main () будет заблокирован и останется в рабочем состоянии для поддержки созданных им потоков до тех пор, пока они не будут выполнены.

Пример: создание и завершение Pthread

Теперь давайте погрузимся в простой пример кода, который создает 5 потоков с помощью подпрограммы pthread_create () и завершается вызовом процедуры pthread_exit ().

#include <pthread.h>
#include <stdio.h>
#define NUM_THREADS 5
void *PrintHello (void *threadid)
{
    long tid;
    tid = (long)threadid;
    printf("Hello World! It's me, thread #%ld!\n", tid);
    pthread_exit(NULL);
}
 int main (int argc, char *argv[])
 {
    pthread_t threads[NUM_THREADS];
    int rc;
    long t;
    for(t=0; t<NUM_THREADS; t++){
       printf("In main: creating thread %ld\n", t);
       rc = pthread_create(&threads[t], NULL, PrintHello, (void *)t);
       if (rc){
          printf("ERROR; return code from pthread_create() is %d\n", rc);
          exit(-1);
       }
    }
    /* Last thing that main() should do */
    pthread_exit(NULL);
 }

Теперь давайте подробнее рассмотрим исходный код в примере. Как и в любой другой программе на языке C, эта программа включает несколько знакомых файлов заголовков, таких как stdio.h. Однако здесь нам также необходимо включить pthread.h, файл заголовка Pthread, в котором объявляются различные функции, константы и типы Pthreads. Затем вы должны определить глобальную переменную для количества потоков, и здесь ей присвоено значение 5. Затем мы использовали две функции pthread_create () и pthread_exit (), которые мы обсуждали ранее. Во-первых, мы выделили хранилище для одного объекта pthread_t для каждого потока. Первый аргумент pthread_create () - это указатель на соответствующий объект pthread_t. Эти объекты pthread_t являются примерами непрозрачных объектов, и поэтому фактические данные, которые они хранят, зависят от системы, а их элементы данных не доступны напрямую для пользовательского кода. Однако стандарт Pthreads гарантирует, что объект pthread_t действительно хранит достаточно информации, чтобы однозначно идентифицировать поток, с которым он связан. Мы не будем использовать второй аргумент, поэтому просто передаем аргумент NULL в вызове нашей функции. Третий аргумент - это функция, которую должен запустить поток, а последний аргумент - указатель на аргумент, который должен быть передан в процедуру запуска функции. Таким образом, окончательный результат будет примерно таким, как показано ниже.

In main: creating thread 0
In main: creating thread 1
Hello World! It's me, thread #0!
In main: creating thread 2
Hello World! It's me, thread #1!
Hello World! It's me, thread #2!
In main: creating thread 3
In main: creating thread 4
Hello World! It's me, thread #3!
Hello World! It's me, thread #4!

Заключение

В этой статье я кратко описал только одну важную часть Pthreads API, а именно управление потоками. Там я показал, как создавать и завершать потоки на простом примере hello world. Однако с API Pthreads можно поиграть гораздо больше, и для начала я предлагаю поиграть с этим примером и посмотреть, как работает параллелизм.

использованная литература

[1] Введение в параллельные вычисления, Computing.llnl.gov, 2020. [Online]. Доступно: https://computing.llnl.gov/tutorials/parallel_comp/. [Доступ: 2 сентября 2020 г.].