Освоение многопоточности в C++: эффективное параллельное программирование
Многопоточность — это специализированная форма многозадачности. Многозадачность позволяет компьютеру запускать две или более программ одновременно. Существует два типа многозадачности: основанная на процессах и основанная на потоках.
- Процессная многозадачность управляет параллельным выполнением программ.
- Потоковая многозадачность обеспечивает одновременное выполнение частей одной программы.
Многопоточная программа содержит два или более потока, каждый из которых определяет отдельный путь выполнения. В C++ нет встроенной поддержки многопоточности — она полностью полагается на ОС. В этом руководстве мы используем POSIX Threads (Pthreads) на Linux. Pthreads доступны во многих Unix-подобных системах: FreeBSD, NetBSD, GNU/Linux, macOS, Solaris.
Создание потоков
Для создания потока POSIX используется функция pthread_create:
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine)(void *), void *arg);
Параметры:
| № | Параметр и описание |
|---|---|
| 1 | thread — уникальный идентификатор нового потока, возвращаемый подпрограммой. |
| 2 | attr — объект атрибутов потока (или NULL для значений по умолчанию). |
| 3 | start_routine — функция, которую выполнит поток после создания. |
| 4 | arg — единственный аргумент, передаваемый в start_routine (указатель void*). NULL, если аргумент не нужен. |
Максимальное число потоков зависит от реализации. После создания потоки равноправны и могут создавать другие потоки; иерархии нет.
Завершение потоков
Для явного завершения потока используется pthread_exit:
#include <pthread.h>
void pthread_exit(void *status);
Обычно вызывается после завершения работы потока. Если main() завершается раньше созданных потоков и вызывает pthread_exit(), остальные потоки продолжают выполняться. Иначе они автоматически завершаются при выходе из main().
Пример: создание 5 потоков
#include <iostream>
#include <cstdlib>
#include <pthread.h>
using namespace std;
#define NUM_THREADS 5
void *PrintHello(void *threadid) {
long tid = (long)threadid;
cout << "Hello World! Thread ID, " << tid << endl;
pthread_exit(NULL);
}
int main() {
pthread_t threads[NUM_THREADS];
int rc;
for (int i = 0; i < NUM_THREADS; i++) {
cout << "main() : creating thread, " << i << endl;
rc = pthread_create(&threads[i], NULL, PrintHello, (void *)i);
if (rc) {
cout << "Error: unable to create thread, " << rc << endl;
exit(-1);
}
}
pthread_exit(NULL);
}
Компиляция: g++ test.cpp -lpthread
Вывод:
main() : creating thread, 0
main() : creating thread, 1
main() : creating thread, 2
main() : creating thread, 3
main() : creating thread, 4
Hello World! Thread ID, 0
Hello World! Thread ID, 1
Hello World! Thread ID, 2
Hello World! Thread ID, 3
Hello World! Thread ID, 4
Передача аргументов в потоки
Можно передать несколько аргументов через структуру. Указатель void* позволяет передать любой тип данных.
#include <iostream>
#include <cstdlib>
#include <pthread.h>
using namespace std;
#define NUM_THREADS 5
struct thread_data {
int thread_id;
char *message;
};
void *PrintHello(void *threadarg) {
struct thread_data *my_data = (struct thread_data *) threadarg;
cout << "Thread ID : " << my_data->thread_id;
cout << " Message : " << my_data->message << endl;
pthread_exit(NULL);
}
int main() {
pthread_t threads[NUM_THREADS];
struct thread_data td[NUM_THREADS];
int rc;
for (int i = 0; i < NUM_THREADS; i++) {
cout << "main() : creating thread, " << i << endl;
td[i].thread_id = i;
td[i].message = "This is message";
rc = pthread_create(&threads[i], NULL, PrintHello, (void *)&td[i]);
if (rc) {
cout << "Error: unable to create thread, " << rc << endl;
exit(-1);
}
}
pthread_exit(NULL);
}
Объединение (join) и отсоединение (detach) потоков
Две функции для синхронизации:
pthread_join(threadid, status)
pthread_detach(threadid)
pthread_join() блокирует вызывающий поток до завершения указанного потока. Поток должен быть создан как joinable (по умолчанию). Отсоединённые потоки нельзя присоединить.
Пример с pthread_join
#include <iostream>
#include <cstdlib>
#include <pthread.h>
#include <unistd.h>
using namespace std;
#define NUM_THREADS 5
void *wait(void *t) {
long tid = (long)t;
sleep(1);
cout << "Sleeping in thread " << endl;
cout << "Thread with id : " << tid << " ...exiting " << endl;
pthread_exit(NULL);
}
int main() {
pthread_t threads[NUM_THREADS];
pthread_attr_t attr;
void *status;
// инициализация атрибутов и установка joinable
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
for (int i = 0; i < NUM_THREADS; i++) {
cout << "main() : creating thread, " << i << endl;
int rc = pthread_create(&threads[i], &attr, wait, (void *)i);
if (rc) {
cout << "Error: unable to create thread, " << rc << endl;
exit(-1);
}
}
// освобождение атрибутов и ожидание завершения потоков
pthread_attr_destroy(&attr);
for (int i = 0; i < NUM_THREADS; i++) {
int rc = pthread_join(threads[i], &status);
if (rc) {
cout << "Error: unable to join, " << rc << endl;
exit(-1);
}
cout << "Main: completed thread id :" << i;
cout << " exiting with status :" << status << endl;
}
cout << "Main: program exiting." << endl;
pthread_exit(NULL);
}
Вывод демонстрирует последовательное завершение потоков и ожидание главного потока.
Язык C
- Освоение операторов C++: полное руководство с практическими примерами
- Комментарии в C++: лучшие практики для читаемого и поддерживаемого кода
- Шаблоны классов в C++: практическое руководство
- Язык программирования C++: обзор и ключевые особенности
- Константы и литералы в C++: типы, синтаксис и лучшие практики
- Полное руководство по операторам C++: типы, использование и лучшие практики
- Работа с числовыми типами в C++: практическое руководство
- Ссылки в C++: ключевые концепции и отличия от указателей
- Шаблоны в C++: руководство по обобщённому программированию
- Освоение многопоточности в C++: эффективное параллельное программирование