Как линковаться на библиотеку pthread
Перейти к содержимому

Как линковаться на библиотеку pthread

  • автор:

How can I link with my own pthread library

I want to make some modifications to the pthread library used for my program. That is why I want to link with my own modified pthread library. I can take the source code in glibc for pthread , modify it and use that for my programs.

Normally you use the flag -pthread for linking with the original pthread library. How do I specify in my makefile to link with my own library.

4 Answers 4

Just use the -L option to specify the directory where your custom lib is present and use -l option to specify the name of your library. For Ex:

In this case your lib name should libmypthread.so

To ensure that the library is found for loading when you execute your program, do

-pthread is equivalent to -lpthread -D_REENTRANT with gcc/glibc. You can do the same, but with a different link argument ( -lname-of-library ).

Don’t use -pthread . It is an undocumented GCC option, probably for compatibility with some other (Solaris?) compilers.

The -D_REENTRANT definition which it -pthread enables is completely unnecessary in glibc; none of the headers depend on this macro for thread safety. (The last of such mechanisms were removed from the glibc headers in 1998!) Simply linking in -lpthread is enough to switch glibc functions to thread safe mode, and -lpthread can be substituted with your own library, like the other answer says.

Pthreads: Потоки в русле POSIX

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

В этой статье мы познакомимся с POSIX Threads для того, чтобы затем узнать как это все работает в Linux. Не заходя в дебри синхронизации и сигналов, рассмотрим основные элементы Pthreads. Итак, под капотом потоки.

Общие сведения

Множественные нити исполнения в одном процессе называют потоками и это базовая единица загрузки ЦПУ, состоящая из идентификатора потока, счетчика, регистров и стека. Потоки внутри одного процесса делят секции кода, данных, а также различные ресурсы: описатели открытых файлов, учетные данные процесса сигналы, значения umask , nice , таймеры и прочее.

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

В чем польза множественных потоков исполнения? Возьмем какой-нибудь загруженный веб сервер, например habrahabr.ru. Если бы сервер создавал отдельный процесс для обслуживания каждого http запроса, мы бы ожидали вечно пока загрузится наша страница. Создания нового процесса — дорогостоящее удовольствие для ОС. Даже учитывая оптимизацию за счет копирования при записи, системные вызовы fork и exec создают новые копии страниц памяти и списка файловых описателей. В целом ядро ОС может создать новый поток на порядок быстрее, чем новый процесс.

Ядро задействует копирование при записи для страниц с данными, сегментов памяти родительского процесса содержащие стек и кучу. Вследствие того, что процессы часто выполняют вызов fork и сразу после этого exec , копирование их страниц во время выполнения вызова fork становится ненужной расточительностью — их все равно приходится отбрасывать после выполнения exec . Сперва записи таблицы страниц указывают на одни и те же страницы физической памяти родительского процесса, сами же страницы маркируются только для чтения. Копирование страницы происходит ровно в тот момент, когда требуется ее изменить.

Таблицы страниц до и после изменения общей страницы памяти во время копирования при записи.

Существует закономерность между количеством параллельных нитей исполнения процесса, алгоритмом программы и ростом производительности. Это зависимость называется Законом Амдаля.

Закон Амдаля для распараллеливания процессов.

Используя уравнение, показанное на рисунке, можно вычислить максимальное улучшение производительности системы, использующей N процессоров и фактор F, который указывает, какая часть системы не может быть распараллелена. Например 75% кода запускается параллельно, а 25% — последовательно. В таком случае на двухядерном процессоре будет достигнуто 1.6 кратное ускорение программы, на четырехядерном процессоре — 2.28571 кратное, а предельное значение ускорения при N стремящемся к бесконечности равно 4.

Отображение потоков в режим ядра

Практически все современные ОС — включая Windows, Linux, Mac OS X, и Solaris — поддерживают управление потоками в режиме ядра. Однако потоки могут быть созданы не только в режиме ядра, но и в режиме пользователя. При использовании этого уровня ядро не знает о существовании потоков — все управление потоками реализуется приложением с помощью специальных библиотек. Пользовательские потоки по разному отображаются на потоки в режиме ядра. Всего существует три модели, из которых 1:1 является наиболее часто используемой.

Отображение N:1

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

Отображение 1:1

Это самая проста модель, в которой каждый поток созданный в каком-нибудь процессе непосредственно управляется планировщиком ядра ОС и отображается на один единственный поток в режиме ядра. Чтобы приложение не плодило бесконтрольно потоки, перегружая ОС, вводят ограничение на максимальное количество потоков поддерживаемых в ОС. Данный способ отображения потоков поддерживают ОС Linux и Windows.

Отображение M:N

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

https://amdy.su/wp-admin/options-general.php?page=ad-inserter.php#tab-8

Потоки POSIX

В конце 1980-х и начале 1990-х было несколько разных API, но в 1995 г. POSIX.1c стандартизовал потоки POSIX, позже это стало частью спецификаций SUSv3. В наше время многоядерные процессоры проникли даже в настольные ПК и смартфоны, так что у большинства машин есть низкоуровневая аппаратная поддержка, позволяющая им одновременно выполнять несколько потоков. В былые времена одновременное исполнение потоков на одноядерных ЦПУ было лишь впечатляюще изобретательной, но очень эффективной иллюзией.

Pthreads определяет набор типов и функций на Си.

  • pthread_t — идентификатор потока;
  • pthread_mutex_t — мютекс;
  • pthread_mutexattr_t — объект атрибутов мютекса
  • pthread_cond_t — условная переменная
  • pthread_condattr_t — объект атрибута условной переменной;
  • pthread_key_t — данные, специфичные для потока;
  • pthread_once_t — контекст контроля динамической инициализации;
  • pthread_attr_t — перечень атрибутов потока.

В традиционном Unix API код последней ошибки errno является глобальной int переменной. Это однако не годится для программ с множественными нитями исполнения. В ситуации, когда вызов функции в одном из исполняемых потоков завершился ошибкой в глобальной переменной errno , может возникнуть состояние гонки из-за того, что и остальные потоки могут в данный момент проверять код ошибки и оконфузиться. В Unix и Linux эту проблему обошли тем, что errno определяется как макрос, задающий для каждой нити собственное изменяемое lvalue .

Создание потока

В начале создается потоковая функция. Затем новый поток создается функцией pthread_create() , объявленной в заголовочном файле pthread.h. Далее, вызывающая сторона продолжает выполнять какие-то свои действия параллельно потоковой функции.

При удачном завершении pthread_create() возвращает код 0, ненулевое значение сигнализирует об ошибке.

  • Первый параметр вызова pthread_create() является адресом для хранения идентификатора создаваемого потока типа pthread_t .
  • Аргумент start является указателем на потоковую void * функцию, принимающей бестиповый указатель в качестве единственной переменной.
  • Аргумент arg — это бестиповый указатель, содержащий аргументы потока. Чаще всего arg указывает на глобальную или динамическую переменную, но если вызываемая функция не требует наличия аргументов, то в качестве arg можно указать NULL .
  • Аргумент attr также является бестиповым указателем атрибутов потока pthread_attr_t . Если этот аргумент равен NULL , то поток создается с атрибутами по умолчанию.

Рассмотрим теперь пример многопоточной программы.

Чтобы подключить библиотеку Pthread к программе, нужно передать компоновщику опцию -lpthread .

О присоединении потока pthread_join расскажу чуть позже. Строка pthread_t tid задает идентификатор потока. Атрибуты функции задает pthread_attr_init(&attr) . Так как мы не задавали их явно, будут использованы значения по умолчанию.

Завершение потока

Поток завершает выполнение задачи когда:

  • потоковая функция выполняет return и возвращает результат произведенных вычислений;
  • в результате вызова завершения исполнения потока pthread_exit() ;
  • в результате вызова отмены потока pthread_cancel() ;
  • одна из нитей совершает вызов exit()
  • основная нить в функции main() выполняет return , и в таком случае все нити процесса резко сворачиваются.

Синтаксис проще, чем при создании потока.

Если в последнем варианте старшая нить из функции main() выполнит pthread_exit() вместо просто exit() или return , то тогда остальные нити продолжат исполняться, как ни в чем не бывало.

Ожидание потока

Функция pthread_join() ожидает завершения потока обозначенного THREAD_ID . Если этот поток к тому времени был уже завершен, то функция немедленно возвращает значение. Смысл функции в том, чтобы синхронизировать потоки. Она объявлена в pthread.h следующим образом:

При удачном завершении pthread_join() возвращает код 0, ненулевое значение сигнализирует об ошибке.

Если указатель DATA отличается от NULL , то туда помещаются данные, возвращаемые потоком через функцию pthread_exit() или через инструкцию return потоковой функции. Несколько потоков не могут ждать завершения одного. Если они пытаются выполнить это, один поток завершается успешно, а все остальные — с ошибкой ESRCH. После завершения pthread_join() , пространство стека связанное с потоком, может быть использовано приложением.

В каком-то смысле pthread_joini() похожа на вызов waitpid() , ожидающую завершения исполнения процесса, но с некоторыми отличиями. Во-первых, все потоки одноранговые, среди них отсутствует иерархический порядок, в то время как процессы образуют дерево и подчинены иерархии родитель — потомок. Поэтому возможно ситуация, когда поток А, породил поток Б, тот в свою очередь заделал В, но затем после вызова функции pthread_join() А будет ожидать завершения В или же наоборот. Во-вторых, нельзя дать указание одному ожидай завершение любого потока, как это возможно с вызовом waitpid(-1, &status, options) . Также невозможно осуществить неблокирующий вызов pthread_join() .

Досрочное завершение потока

Точно так же, как при управлении процессами, иногда необходимо досрочно завершить процесс, многопоточной программе может понадобиться досрочно завершить один из потоков. Для досрочного завершения потока можно воспользоваться функцией pthread_cancel .

При удачном завершении pthread_cancel() возвращает код 0, ненулевое значение сигнализирует об ошибке.

Важно понимать, что несмотря на то, что pthread_cancel() возвращается сразу и может завершить поток досрочно, ее нельзя назвать средством принудительного завершения потоков. Дело в том, что поток не только может самостоятельно выбрать момент завершения в ответ на вызов pthread_cancel() , но и вовсе его игнорировать. Вызов функции pthread_cancel() следует рассматривать как запрос на выполнение досрочного завершения потока. Поэтому, если для вас важно, чтобы поток был удален, нужно дождаться его завершения функцией pthread_join() .

Небольшая иллюстрация создания и отмены потока.

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

Как мы видим есть вовсе неотменяемые потоки, а поведением по умолчанию является отложенное завершение, которое происходит в момент завершения. А откуда мы узнаем, что этот самый момент наступил? Для этого существует вспомогательная функция pthread_testcancel .

Отсоединение потока

Любому потоку по умолчанию можно присоединиться вызовом pthread_join() и ожидать его завершения. Однако в некоторых случаях статус завершения потока и возврат значения нам не интересны. Все, что нам надо, это завершить поток и автоматически выгрузить ресурсы обратно в распоряжение ОС. В таких случаях мы обозначаем поток отсоединившимся и используем вызов pthread_detach() .

При удачном завершении pthread_detach() возвращает код 0, ненулевое значение сигнализирует об ошибке.

Отсоединенный поток — это приговор. Его уже не перехватить с помощью вызова pthread_join() , чтобы получить статус завершения и прочие плюшки. Также нельзя отменить его отсоединенное состояние. Вопрос на засыпку. Что будет, если завершение потока не перехватить вызовом pthread_join() и чем это отлично от сценария, при котором завершился отсоединенный поток? В первом случае мы получим зомбо-поток, а во втором — все будет норм.

Потоки versus процессы

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

В начальной части статьи мы уже указывали на эти преимущество, поэтому вкратце их просто перечислим.

  • Потоки довольно просто обмениваются данными по сравнению с процессами.
  • Создавать потоки для ОС проще и быстрее, чем создавать процессы.

Теперь немного о недостатках.

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

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

Name already in use

HOWTO / entries / link-pthread.md

  • Go to file T
  • Go to line L
  • Copy path
  • Copy permalink
  • Open with Desktop
  • View raw
  • Copy raw contents Copy raw contents

Copy raw contents

Copy raw contents

How do I link the pthread library?

How do I link the pthread library?

How do I deal with undefined pthread_create when using C++11 <thread> ?

The short version: for modern pthread -based targets (Linux, FreeBSD, MacOS, MinGW) add the -pthread option to *.libs (you can think of it as a special alias for -lpthread ). For example, in a C++ project:

Additionally, in case of a library, if the pthread API (or one of the C++11 multi-threading APIs found in <thread> , <mutex> , <condition_variable> , or <future> ) are used in the library’s interface (for example, called from inline/template functions), then also add -pthread to *.export.libs . For example:

The rest of this article provides some background on the underlying problem and shows how to handle legacy pthread -based targets.

In the old days, building multi-threaded applications required special actions both during compilation and during linking. Specifically, during compilation we would need to define a macro, typically _REENTRANT , to make sure that we use thread-safe versions of the C/POSIX APIs. And during linking we would need to link the pthread library, typically as -lpthread .

To help achieve this in a portable manner, the -pthread option was invented with the expectation that the user will specify it both during compilation (where it would be translated to the necessary macro definitions) and during linking (where it would be translated to -lpthread or equivalent).

Two things have been happening since those old days: Firstly, most modern operating systems have been moving to the always thread-safe C/POSIX APIs thus no longer requiring any macros during compilation (see __libc_single_threaded for an example of an alternative way to provide single-threaded optimizations). And, secondly, the pthread library were being merged into libc thus no longer requiring -lpthread during linking.

The first change is complete, at least for the commonly-used general-purpose operating systems: Linux with glibc , FreeBSD, MacOS, and MinGW. The second change is still happening: MacOS were already there for some time, Linux is since glibc 2.34, while MinGW and FreeBSD still require -lpthread , at least as of version FreeBSD 13 and MinGW GCC 11.

This background should make the motivation behind the above recommendation clear: we normally no longer need to specify -pthread during compilation and by using -pthread instead of -lpthread during linking we allow the compiler to translate it into nothing if linking a separate library for pthread is no longer necessary on the target platform.

Legacy and «high touch» targets

There are situations where we may still need to specify -pthread during compilation. These include:

Legacy targets which still have the single/multi-threaded C/POSIX APIs distinction and thus require macro definitions.

Legacy third-party libraries that follows the same approach for their APIs.

Targets where multi-threading requires a «high-touch» approach, such as Emscripten.

pthread.h в Visual Studio

Установка библиотеки pthread.h в среде Visual Studio

Н а примере Visual Studio 2012 Express Edition, Windows 7. Воспользуемся ресурсом ftp://sourceware.org/pub/pthreads-win32/dll-latest

  • 1. Скачайте все заголовочные *.h файлы из папки include и поместите их в папку include среды Visual Studio.
    Папка располагается примерно по такому адресу C:\Program Files\Microsoft Visual Studio 12.0\VC\include
  • 2. На ftp сервере перейдите в папку bin, далее в папку с подходящей архитектурой. Скопируйте в папку bin студии файл pthreadVSE2.dll.
    У меня эта папка располагается по адресу C:\Program Files\Microsoft Visual Studio 12.0\VC\bin
  • 3. Перейдите в папку lib, далее в папку с вашей архитектурой. Скопируйте файл pthreadVSE2.lib в папку lib студии.
    У меня она располагается по адресу C:\Program Files\Microsoft Visual Studio 12.0\VC\lib

Теперь появится возможность добавлять библиотеку pthread.h: Visual Studio увидит её, будет подсвечивать синтаксис и выводить подсказки.

  • 4. Создайте пустой проект. Откройте окно «Свойства проекта». Для этого либо кликните правой кнопкой мыши по имени проекта в Обозревателе решений | Свойства , либо откройте Проект | Свойства .
    Во вкладке Свойства конфигурации | Компоновщик | Ввод добавьте справа в дополнительные зависимости имя библиотеки pthreadVSE2.lib.

Каждый новый проект потребует этого шага. Для проверки напишем простое приложение

Вместо pthreadVSE2 можно качать и устанавливать файлы pthreadVC2. Но тогда и в дополнительных зависимостях придётся писать pthreadVC2.lib.

email

Всё ещё не понятно? – пиши вопросы на ящик

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

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