как добавить в cmake -pthread?
Есть cmake файл, как добавить в него посикс потоки -pthread и библиотеку буста(установлен в системе) на примере boost.thread?
cmake_minimum_required(VERSION 3.17) project(test) set(CMAKE_CXX_STANDARD 20) add_executable(test main.cpp) find_package(Boost REQUIRED)
Отслеживать
задан 13 сен 2020 в 11:29
Denver Toha Denver Toha
2,595 1 1 золотой знак 12 12 серебряных знаков 32 32 бронзовых знака
13 сен 2020 в 16:54
1 ответ 1
Сортировка: Сброс на вариант по умолчанию
Если CMake свежий (3.1+), тогда так
set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) target_link_libraries(my_app PRIVATE Threads::Threads)
Если лень что то писать и разбираться, заработает где то так, но это для линукса
SET(CMAKE_CXX_FLAGS_DEBUG ". -lpthread") SET(CMAKE_CXX_FLAGS_RELEASE ". -lpthread")
но это если совсем лень.
Почему линкуется библиотека?
Есть библиотека например «Base» использующую pthread, и программа которая ее линкует к себе например «Program».
Если собрать «Base» статически то и «Program» соберется, а если «Base» собрать динамически то «Program» требует чтобы прилинковали «pthread».
Почему в одном случае нужно линковать pthread к «Program» а в другом нет?
- Вопрос задан более трёх лет назад
- 290 просмотров
3 комментария
Оценить 3 комментария
Ошибка undefined reference to `pthread_create’. Как исправить?
В KDevelop не получается собрать программу с потоком.
В программе подключен модуль
#include
Потом вызывается поток
thread mythr(myfunc2);
При попытке собрать получается такая ошибка: /home/evg/projects/myprog2/build> make -j1 -lpthread -- Configuring done -- Generating done -- Build files have been written to: /home/evg/projects/myprog2/build [ 50%] Linking CXX executable myprog2 CMakeFiles/myprog2.dir/main.cpp.o: In function `std::thread::thread(void (&)())': /usr/include/c++/7/thread:122: undefined reference to `pthread_create' collect2: error: ld returned 1 exit status make[2]: *** [CMakeFiles/myprog2.dir/build.make:95: myprog2] Error 1 make[1]: *** [CMakeFiles/Makefile2:68: CMakeFiles/myprog2.dir/all] Error 2 make: *** [Makefile:130: all] Error 2 *** Ошибка: Код выхода: 2 ***
Как исправить? Как подключить модуль thread?
Unix2019b/Библиотека pthreads
Библиотека pthreads определяет набор типов, функций, констант для языка C. Заголовочный файл называется pthread.h.
Там около сотни функций, все начинаются на pthread_, могут быть отнесены к четырём группам:
- Thread management — creating, joining threads etc.
- Mutexes
- Condition variables
- Synchronization between threads using read/write locks and barriers
Важно: функции не выставляют значение errno. Большинство функций библиотеки спроектированы так, что возвращают 0 в случае успеха и код ошибки (положительное число) в случае неудачи.
Само по себе errno реализуется не просто как глобальная переменная, а как макрос, который раскрывается в thread-specific-переменную. Поэтому обрабатывать ошибки POSIX-функций через errno в разных потоках можно безопасно, состояния гонки при этом не возникает.
Пример
#include #include #include #include #define NUM_THREADS 5 void* perform_work(void* argument) { int passed_in_value; passed_in_value = *((int*) argument); printf("Hello World! It's me, thread with argument %d!\n", passed_in_value); return NULL; } int main(int argc, char** argv) { pthread_t threads[NUM_THREADS]; int thread_args[NUM_THREADS]; int result_code; unsigned index; // create all threads one by one for (index = 0; index NUM_THREADS; ++index) { thread_args[ index ] = index; printf("In main: creating thread %d\n", index); result_code = pthread_create(&threads[index], NULL, perform_work, &thread_args[index]); assert(!result_code); } // wait for each thread to complete for (index = 0; index NUM_THREADS; ++index) { // block until thread 'index' completes result_code = pthread_join(threads[index], NULL); assert(!result_code); printf("In main: thread %d has completed\n", index); } printf("In main: All threads completed successfully\n"); exit(EXIT_SUCCESS); }
Сборка
Требуется указание флага pthread [1].
$ gcc main.c -pthread
Этот флаг включает макросы а-ля _REENTERANT и велит линковать программу с libpthread.so.
Создание и завершение потоков
#include int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); void pthread_exit(void *retval); int pthread_join(pthread_t thread, void **retval);
Потоки создаются функцией pthread_create.
- Первый параметр этой функции представляет собой указатель на переменную типа pthread_t, которая служит идентификатором создаваемого потока.
- Второй параметр, указатель на переменную типа pthread_attr_t, используется для передачи атрибутов потока.
- Третьим параметром функции должен быть адрес функции потока. Эта функция играет для потока ту же роль, что функция main() – для главной программы.
- Четвертый параметр функции pthread_create() имеет тип void *. Этот параметр может использоваться для передачи значения, возвращаемого функцией потока.
Вскоре после вызова pthread_create() функция потока будет запущена на выполнение параллельно с другими потоками программы. Таким образом, собственно, и создается новый поток. Если в ходе создания потока возникла ошибка, функция pthread_create() возвращает ненулевое значение, соответствующее номеру ошибки.
Функция потока должна иметь сигнатуру вида
void* func_name(void* arg)
Имя функции, естественно, может быть любым. Аргумент arg — это тот самый указатель, который передаётся в последнем параметре функции pthread_create(). Функция потока может вернуть значение, которое затем будет проанализировано заинтересованным потоком, но это не обязательно. Завершение функции потока происходит если:
- функция потока вызвала функцию pthread_exit;
- функция потока достигла точки выхода;
- поток был досрочно завершен другим потоком.
Функция pthread_exit представляет собой потоковый аналог функции _exit. Аргумент, значение типа void *, становится возвращаемым значением функции потока.
Как (и кому?) функция потока может вернуть значение, если она не вызывается из программы явным образом? Для того чтобы получить значение, возвращенное функцией потока, нужно воспользоваться функцией pthread_join. У этой функции два параметра.
- Первый параметр – это идентификатор потока.
- Второй параметр имеет тип «указатель на нетипизированный указатель». В этом параметре функция pthread_join() возвращает значение, возвращенное функцией потока.
Конечно, в многопоточном приложении есть и более простые способы организовать передачу данных между потоками. Основная задача функции pthread_join() заключается, однако, в синхронизации потоков. Эта функция позволяет вызвавшему ее потоку дождаться завершения работы другого потока. Вызов функции pthread_join() приостанавливает выполнение вызвавшего ее потока до тех пор, пока поток, чей идентификатор передан функции в качестве аргумента, не завершит свою работу. Если в момент вызова pthread_join() ожидаемый поток уже завершился, функция вернет управление немедленно.
Функцию pthread_join() можно рассматривать как эквивалент waitpid() для потоков, но с некоторыми отличиями.
- Все потоки одноранговые, среди них отсутствует иерархический порядок, в то время как процессы образуют дерево и подчинены иерархии родитель — потомок.
- Нельзя дать указание одному «ожидай завершения любого потока», как это возможно с вызовом waitpid(-1, &status, options).
- Также невозможно осуществить неблокирующий вызов pthread_join().
Отсоединение потока
Любому потоку по умолчанию можно присоединиться вызовом pthread_join() и ожидать его завершения. Однако в некоторых случаях статус завершения потока и возврат значения нам не интересны. Все, что нам надо, это завершить поток и автоматически выгрузить ресурсы обратно в распоряжение ОС. В таких случаях мы обозначаем поток отсоединившимся и используем вызов pthread_detach().
#include int pthread_detach(pthread_t thread);
При удачном завершении pthread_detach() возвращает код 0, ненулевое значение сигнализирует об ошибке.
Если поток отсоединён, его уже не перехватить с помощью вызова pthread_join(), чтобы получить статус завершения. Также нельзя отменить его отсоединенное состояние.
Примитивы синхронизации
Предполагается, что студенты ранее знакомились с примитивами синхронизации на первом-втором курсе, поэтому изложение будет очень кратким.
Mutex
Мьютексы — это простейшие двоичные семафоры, которые могут находиться в одном из двух состояний — отмеченном или неотмеченном (открыт и закрыт соответственно). Задача мьютекса — защита объекта от доступа к нему других потоков, отличных от того, который завладел мьютексом. В каждый конкретный момент только один поток может владеть объектом, защищённым мьютексом. Если другому потоку будет нужен доступ к переменной, защищённой мьютексом, то этот поток блокируется до тех пор, пока мьютекс не будет освобождён.
Цель использования мьютексов — защита данных от повреждения в результате асинхронных изменений (состояние гонки), однако могут порождаться другие проблемы (например взаимные блокировки).
Мьютекс в pthreads имеет тип pthread_mutex_t (переменная этого типа занимает 24 байта на 32-битных системах и 40 байт на 64-битных).
Поддерживаются следующие основные операции.
pthread_mutex_init — инициализация. Поскольку в C нет конструкторов, то такая функция выполняет его роль. Другим способом проинициализировать мьютекс перед использованием является применение специального инициализатора [2]:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
По стандарту такой инициализатор подходит только для глобальных переменных. Если его использовать для обычной локальной переменной на стеке, то код скомпилируется, но теоретически на каких-то платформах может работать неверно (если макрос раскрывается в вызов непотокобезопасной функции).
pthread_mutex_lock — операция захвата мьютекса. Если мьютекс занят, функция блокируется до тех пор, пока мьютекс не станет доступен. В случае успеха возвращает ноль.
pthread_mutex_unlock — операция освобождения ранее захваченного мьютекса.
Мьютексы в pthreads бывают разных типов (обычный, рекурсивный, с проверкой ошибок). Это можно настраивать, передавая аргумент в функцию init. Например, созданный с опцией PTHREAD_MUTEX_RECURSIVE мьютекс обладает свойством подсчёта локов. Когда поток захватывает мьютекс, счётчик выставляется в 1. Каждый последующий захват этого же мьютекса этим же потоком приводит к увеличению счётчика на единицу, о освобождение — к уменьшению на единицу.
Condition variable
Условная переменная — примитив синхронизации, обеспечивающий блокирование одного или нескольких потоков до момента поступления уведомления от другого потока о выполнении некоторого условия или до истечения максимального промежутка времени ожидания. Условные переменные используются вместе с ассоциированным мьютексом.
Мьютекс не позволяет нескольким потокам обращаться к общей переменной одновременно. Условная переменная позволяет одному потоку сообщать другим потокам об изменении состояния переменной (или другого общего ресурса) и позволяет другим потокам ждать такого уведомления (блокироваться до наступления события).
В pthreads используется тип данных pthread_cond_t.
pthread_cond_init выполняет инициализацию условной переменной. Или же можно использовать
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_cond_signal запускает один из потоков, которые в настоящий момент ожидают на данной условной переменной. Если таких потоков нет, ничего не происходит, сигнал теряется. Если их несколько, то разблокируется ровно один, но это может быть любой поток.
pthread_cond_broadcast запускает все потоки, ожидающие на этой условной переменной.
pthread_cond_wait принимает не только условную переменную, но и мьютекс. Мьютекс должен быть заранее захвачен. Функция атомарно разблокирует мьютекс и ждёт, пока условная переменная не будет просигнализирована. Ожидание не потребляет тактов процессора. Наконец, когда ожидание завершено, перед возвратом управления мьютекс заново захватывается. Типичная последовательность действий выглядит так:
pthread_mutex_lock(mutex); while (!predicate) { pthread_cond_wait(cvar); } pthread_mutex_unlock(mutex);
Атомарность, заложенная в функцию wait, гарантирует, что какой-то другой поток не может захватить мьютекс и сигнализировать условную переменную до того, как текущий поток начал ожидать сигнала на этой условной переменной.
Может возникнуть вопрос, вызывать сначала pthread_cond_signal, потом pthread_mutex_unlock, или наоборот. На самом деле можно делать и так и так. В литературе замечают, что чаще для лучшей производительности лучше сначала отпускать мьютекс, потом сигнализировать, чтобы не случалось лишних пробуждений и переключений контекста, но в зависимости от реализации разницы может и не быть [3].
При ожидании на условной переменной возможны так называемые ложные пробуждения (англ. spurious wakeups) [4]. То есть, согласно стандарту, функция может вернуть управление даже в случае, когда никто не вызывал signal. Поэтому wait всегда нужно вызывать в цикле, проверяя на каждой итерации выполнение нужного условия.
Пример: производители и потребители
Пример из Википедии.
#include #include #include #include #define STORAGE_MIN 10 #define STORAGE_MAX 20 /* Разделяемый ресурс */ int storage = STORAGE_MIN; pthread_mutex_t mutex; pthread_cond_t condition; /* Функция потока потребителя */ void *consumer(void *args) { puts("[CONSUMER] thread started"); int toConsume = 0; while(1) { pthread_mutex_lock(&mutex); /* Если значение общей переменной меньше максимального, * то поток входит в состояние ожидания сигнала о достижении * максимума */ while (storage STORAGE_MAX) { pthread_cond_wait(&condition, &mutex); } toConsume = storage-STORAGE_MIN; printf("[CONSUMER] storage is maximum, consuming %d\n", toConsume); /* "Потребление" допустимого объема из значения общей * переменной */ storage -= toConsume; printf("[CONSUMER] storage = %d\n", storage); pthread_mutex_unlock(&mutex); } return NULL; } /* Функция потока производителя */ void *producer(void *args) { puts("[PRODUCER] thread started"); while (1) { usleep(200000); pthread_mutex_lock(&mutex); /* Производитель постоянно увеличивает значение общей переменной */ ++storage; printf("[PRODUCER] storage = %d\n", storage); /* Если значение общей переменной достигло или превысило * максимум, поток потребитель уведомляется об этом */ if (storage >= STORAGE_MAX) { puts("[PRODUCER] storage maximum"); pthread_cond_signal(&condition); } pthread_mutex_unlock(&mutex); } return NULL; } int main(int argc, char *argv[]) { int res = 0; pthread_t thProducer, thConsumer; pthread_mutex_init(&mutex, NULL); pthread_cond_init(&condition, NULL); res = pthread_create(&thProducer, NULL, producer, NULL); if (res != 0) { perror("pthread_create"); exit(EXIT_FAILURE); } res = pthread_create(&thConsumer, NULL, consumer, NULL); if (res != 0) { perror("pthread_create"); exit(EXIT_FAILURE); } pthread_join(thProducer, NULL); pthread_join(thConsumer, NULL); return EXIT_SUCCESS; }
Thread-Local Storage
Чтобы создать переменную, локальную для каждого потока, нужно при объявлении глобальной или локальной статической переменной использовать спецификатор __thread.
static __thread char buf[MAX_ERROR_LEN];
При этом каждый поток получит свою копию.
В стандарте C11 добавили аналогичный спецификатор _Thread_local.
Потокобезопасность
Функция называется потокобезопасной (thread-safe), если она может безопасно вызываться несколькими потоками одновременно; и наоборот, если функция не является потокобезопасной, то мы не можем вызывать ее из одного потока, пока она выполняется в другом потоке.
Функция называется реентерабельной (reentrant), если она разработана таким образом, что несколько вызовов могут безопасно выполняться одновременно. Это важно даже в однопоточных программах, так как выполнение может быть прервано обработкой сигнала.
Из одного понятия не следует другое понятие. Примеры.
ThreadSanitizer
ThreadSanitizer (или TSan) — детектор состояния гонки (data race) для C и С++. Гонки являются одним из наиболее распространённых и трудных для отладки типов ошибок в параллельных системах. Гонка наблюдается, когда два потока одновременно обращаются к одной и той же переменной и по крайней мере одно из обращений — запись.