Invest-currency.ru

Как обезопасить себя в кризис?
1 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

C linux потоки

Различие между процессами и потоками в Linux

после прочтения на ответ и «разработка ядра Linux» Робертом Лав и, впоследствии, на clone() системный вызов, я обнаружил, что процессы и потоки в Linux (почти) неразличимы для ядра. Между ними есть несколько настроек (обсуждается как «больше обмена» или «меньше обмена» в процитированном вопросе SO), но у меня все еще есть некоторые вопросы, на которые еще нужно ответить.

недавно я работал над программой, включающей пару POSIX и решил поэкспериментировать с этой предпосылкой. В процессе, который создает два потока, все потоки, конечно, получают уникальное значение, возвращаемое pthread_self() , , а не getpid() .

образец программы, которую я создал, следующий:

(это было скомпилировано [ gcc -pthread -o thread_test thread_test.c ] на 64-разрядной Fedora; из-за 64-разрядных типов, используемых для pthread_t полученные от , код потребует незначительных изменений для компиляции в 32-разрядных версиях.)

выход Я получаю следующее:

С помощью планировщика блокировки в gdb , я могу сохранить программу и ее потоки, чтобы я мог захватить то, что top говорит, что, просто показываю процессов, является:

и, показывая темы, говорит:

кажется совершенно ясным, что программы или, возможно, ядро имеют отличный способ определения потоков в отличие от процессов. Каждый поток имеет свой собственный PID в соответствии с top — почему?

3 ответов

все эти путаницы проистекают из того факта, что разработчики ядра изначально придерживались иррационального и неправильного представления о том, что потоки могут быть реализованы почти полностью в пользовательском пространстве, используя процессы ядра в качестве примитива, пока ядро предлагает способ заставить их делиться памятью и файловыми дескрипторами. Это привело к печально известной плохой реализации LinuxThreads потоков POSIX, которая была довольно неправильным названием, потому что она не давала ничего, отдаленно напоминающего семантику потока POSIX. В конце концов LinuxThreads был заменен (NPTL), но многие запутанные термины и недоразумения сохраняются.

первое и самое главное, чтобы понять, что «PID» означает разные вещи в пространстве ядра и пространстве пользователя. То, что ядро называет PID, на самом деле является идентификаторами потока уровня ядра (часто называемыми TIDs), не путать с pthread_t который является отдельным идентификатором. Каждый поток в системе, будь то в том же процессе или другом, имеет уникальный TID (или «PID» в терминологии ядра).

то, что считается PID в POSIX смысле «процесса», с другой стороны, называется «ID группы потоков» или «TGID» в ядре. Каждый процесс состоит из одного или нескольких потоков (процессов ядра) каждый со своим собственным TID (kernel PID), но все они имеют один и тот же TGID, который равен TID (kernel PID) исходного потока, в котором main работает.

, когда top показывает вам потоки, он показывает TIDs (PIDs ядра), а не PIDs (tgids ядра), и именно поэтому каждый поток имеет отдельный.

С появлением NPTL большинство системных вызовов, которые принимают аргумент PID или действуют на вызов

представьте себе какую-то»мета-сущность». Если сущность не использует ни один из ресурсов (адресное пространство, файловые дескрипторы и т. д.) своего родителя, то это процесс, а если сущность использует все ресурсы своего родителя, то это поток. Вы даже можете иметь что-то на полпути между процессом и потоком (например, некоторые общие ресурсы и некоторые не общие). Взгляните на системный вызов «clone ()» (например,http://linux.die.net/man/2/clone ) и вы увидите, как это делает Linux все внутренне.

теперь спрячьте это за какой-то абстракцией, которая делает все похожим на процесс или поток. Если абстракция безупречна, вы никогда не узнаете разницу между «сущностями» и «процессами и потоками». Абстракция не совсем безупречна, хотя-PID, который вы видите, на самом деле является»идентификатором сущности».

в Linux каждый поток получает поток код. Идентификатор потока основного потока выполняет двойную функцию в качестве идентификатора процесса (и довольно хорошо известен в пользовательском интерфейсе). Идентификатор потока является деталью реализации Linux и не связан с идентификатором POSIX. Для получения более подробной информации обратитесь к gettid системный вызов (недоступен из pure Python, так как он специфичен для системы).

Создание и ожидание выполнения потоков

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

Р ассмотрим простой пример

В данном примере внутри основного потока, в котором работает функция main, создаётся новый поток, внутри которого вызывается функция helloWorld. Функция helloWorld выводит на дисплей приветствие. Внутри основного потока также выводится приветствие. Далее потоки объединяются.

Новый поток создаётся с помощью функции pthread_create

Функция получает в качестве аргументов указатель на поток, переменную типа pthread_t, в которую, в случае удачного завершения сохраняет id потока. pthread_attr_t – атрибуты потока. В случае если используются атрибуты по умолчанию, то можно передавать NULL. start_routin – это непосредственно та функция, которая будет выполняться в новом потоке. arg – это аргументы, которые будут переданы функции.

Поток может выполнять много разных дел и получать разные аргументы. Для этого функция, которая будет запущена в новом потоке, принимает аргумент типа void*. За счёт этого можно обернуть все передаваемые аргументы в структуру. Возвращать значение можно также через передаваемый аргумент.

Читать еще:  Linux mint не находит bluetooth устройства

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

  • EAGAIN – у системы нет ресурсов для создания нового потока, или система не может больше создавать потоков, так как количество потоков превысило значение PTHREAD_THREADS_MAX (например, на одной из машин, которые используются для тестирования, это магическое число равно 2019)
  • EINVAL – неправильные атрибуты потока (переданные аргументом attr)
  • EPERM – Вызывающий поток не имеет должных прав для того, чтобы задать нужные параметры или политики планировщика.

Пройдём по программе

Здесь мы задаём набор значений, необходимый для обработки возможных ошибок.

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

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

Приводит к тому, что основной поток будет ждать завершения порождённого. Функция

Откладывает выполнение вызывающего (эту функцию) потока, до тех пор, пока не будет выполнен поток thread. Когда pthread_join выполнилась успешно, то она возвращает 0. Если поток явно вернул значение (это то самое значение SUCCESS, из нашей функции), то оно будет помещено в переменную value_ptr. Возможные ошибки, которые возвращает pthread_join

  • EINVAL – thread указывает на не объединяемый поток
  • ESRCH – не существует потока с таким идентификатором, который хранит переменная thread
  • EDEADLK – был обнаружен дедлок (взаимная блокировка), или же в качестве объединяемого потока указан сам вызывающий поток.

Пример создания потоков с передачей им аргументов

П усть мы хотим передать потоку данные и вернуть что-нибудь обратно. Скажем, передавать потоку будем строку, а возвращать из потока длину этой строки.

Так как функция может получать только указатель типа void, то все аргументы следует упаковать в структуру. Определим новый тип структуру:

Здесь id – это идентификатор потока (он в общем-то не нужен в нашем примере), второе поле это строка, а третье длина строки, которую мы будем возвращать.

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

В том случае, если всё прошло удачно, то в качестве статуса возвращаем значение SUCCESS, а если была допущена ошибка (в нашем случае, если передана нулевая строка), то выходим со статусом BAD_MESSAGE.

В этом примере создадим 4 потока. Для 4-х потоков понадобятся массив типа pthread_t длинной 4, массив передаваемых аргументов и 4 строки, которые мы и будем передавать.

Первым делом заполняем значения аргументов.

Далее создаём в цикле новые потоки

Затем ждём завершения

Под конец ещё выводим аргументы, которые теперь хранят возвращённые значения. Заметьте, что один из аргументов «плохой» (строка равна NULL). Вот полный код

Выполните его несколько раз. Заметьте, что порядок выполнения потоков не детерминирован. Запуская программу, можно каждый раз получить другой порядок выполнения.

Синхронизация потоков в OC Linux

Если вы хотя бы раз разрабатывали многопоточное приложение, не важно под какую ос — думаю вы сталкивались с понятием синхронизации. Т.е. я не собираюсь досконально разбирать что это такое и для чего нужно — есть масса руководств по этой теме :). Здесь будет рассматриваться один конкретный случай синхронизации потоков применительно к ОС Linux. И так приступим.

Для создания потока используется библиотека pthread и вызов pthread_create, для синхронизации в этой же библиотеке описаны специальные объекты — мутексы. Мутекс — это объект который может принадлежать в некий момент времени только одному потоку и имеющий два состояния — занят и свободен. Поток пытающийся получить доступ к мутексу в случае если последний занят будет остановлен системой до освобождения объекта. На этом собственно и основана синхронизация — перед использованием общего ресурса потоки сначала обращаются к мутексу и в конечном счете выстраиваются в очередь.

Все отлично работает — но рассмотрим ситуацию когда есть много потоков выполняющих только чтение некоего ресурса(они не нуждаются в синхронизации между собой) и один или несколько потоков выполняющих изменение этого ресурса. Т.е. синхронизировать нужно этот пишущий поток со всеми читателями. Если просто использовать мутекс для синхронизации доступа к ресурсу очевидно что и потоки чтения будут получать доступ туда последовательно, что в свою очередь замедлит приложение, фактически превратив его в однопоточное :). В этой ситуации одним из вариантов решения будет применение семафора. Семафор — это специальный объект ядра предназначенный для взаимодействия процессов в системе. Не буду утомлять описанием этого объекта, это прекрасно сделали до меня, например тут Linux Interprocess Communications. Упомяну только что в системе создается именованное множество семафоров(содержащее минимум 1 семафор), каждый семафор содержит некое количество ресурсов выраженное целым числом. Поток может запрашивать ресурсы у семафора и естественно должен отдавать их обратно когда они более не нужны. В случае если запрошенное количество ресурсов недоступно, но меньше максимального имеющегося количества — поток ожидает освобождения(в случае если не установлена опция — без ожидания)- иначе возвращается ошибка. Таким образом семафор более гибкий механизм синхронизации(однако неприятность в том что это объект ядра). Попробуем применить его к озвученной выше задаче.

Читать еще:  C datetime to unixtime

Для облегчения работы рассмотрим объектный интерфейс реализующий множество семафоров состоящее из одного семафора: В примере 2 файла — можно безболезненно слить их в один, например sem.cpp 🙂

Создадим семафор с 10 ресурсами например, читающие потоки будут запрашивать по 1 ресурсу за раз, пишущий сразу 10. В итоге читающие потоки будут блокировать друг друга только в случае если их запущенное число превысит 10(нужно самостоятельно подбирать нужное значение). Поток записи же при старте будет гарантированно ждать освобождения ресурса всеми потоками чтения и заставлять ждать позднее стартующие потоки окончания своей работы. Хочу обратить внимание на один нюанс — перед запросом получения ресурса потоком чтения следует ставить запрос на разрешение обратиться за получением этого самого ресурса. Т.е. поток чтения в случае если не установлен некий флаг разрешения зацикливается и ждет пока его установят, только после этого приступая к запросу. Это нужно для того чтобы потоки чтения не оттесняли поток записи в конец цикла ожидания — например освобождено уже 9 ресурсов из 10, поток записи ждет, но запускается поток чтения, запрашивает ресурс и естественное его получает — в итоге 8 свободных ресурсов :), запись оттесняется. Кстати очень удобно если требуется выполнение какой-либо работы во время простоя системы :).

Рассмотрим пример программы:

Собирается эта программа очень просто: g++ -g stest.cpp sem.cpp -lpthread -o stest

Вывод примерно такой:

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

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

Многопоточность в C

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

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

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

rmuhamma/OpSystems/Myos/threads.htm для получения более подробной информации.

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

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

#include
#include
#include //Header file for sleep(). man 3 sleep for details.
#include

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

void *myThreadFun( void *vargp)

printf ( «Printing GeeksQuiz from Thread n» );

printf ( «Before Threadn» );

printf ( «After Threadn» );

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

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

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

Читать еще:  C compiler linux

#include
#include
#include
#include

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

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

void *myThreadFun( void *vargp)

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

int *myid = ( int *)vargp;

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

static int s = 0;

// Изменить статические и глобальные переменные

// Распечатать аргумент, статические и глобальные переменные

printf ( «Thread ID: %d, Static: %d, Global: %dn» , *myid, ++s, ++g);

Различие между процессами и потоками в Linux

17 Doddy [2012-02-06 04:22:00]

После прочтения этого ответа и «Развитие ядра Linux» Роберта Лава и, впоследствии, системного вызова clone() , я обнаружил, что процессы и потоки в Linux (почти) неотличимы от ядра. Есть несколько настроек между ними (обсуждается как «более общий доступ» или «меньше обмена» в запрошенном вопросе SO), но у меня все еще есть некоторые вопросы, на которые еще нужно ответить.

Недавно я работал над программой, включающей пару потоков POSIX, и решил поэкспериментировать с этой предпосылкой. В процессе, который создает два потока, все потоки, конечно, получают уникальное значение, возвращаемое pthread_self() , но не getpid() .

Ниже приведен пример программы, которую я создал:

(Это было скомпилировано [ gcc -pthread -o thread_test thread_test.c ] в 64-битной Fedora, из-за 64-разрядных типов, используемых для pthread_t , полученных из , для компиляции кода в 32-разрядных версиях код потребует незначительных изменений. )

Выход, который я получаю, выглядит следующим образом:

Используя блокировку планировщика в gdb , я могу сохранить программу и ее потоки живыми, чтобы я мог записать, что говорит top , что, только что показывающее процессы, есть:

И при показе потоков, говорит:

Понятно, что программы, или, возможно, ядро, имеют четкий способ определения потоков в отличие от процессов. Каждый поток имеет свой собственный PID в соответствии с top — почему?

c multithreading linux

3 ответа

29 Решение R.. [2012-02-06 04:33:00]

Все эти путаницы связаны с тем, что разработчики ядра изначально считали иррациональное и неправильное представление о том, что потоки могут быть реализованы почти полностью в пользовательском пространстве с использованием процессов ядра в качестве примитива, при условии, что ядро ​​предложило способ совместного использования памяти и файловые дескрипторы. Это привело к заведомо плохой реализации LinuxThreads потоков POSIX, что было довольно неправильным, потому что это не дало ничего отдаленно напоминающего семантику потоков POSIX. В итоге LinuxThreads был заменен (NPTL), но многие запутанные термины и недоразумения сохраняются.

Первым и самым важным для понимания является то, что «PID» означает разные вещи в пространстве ядра и пространстве пользователя. То, что ядро ​​вызывает PID, на самом деле — это идентификаторы потоков на уровне ядра (часто называемые TID), а не путать с pthread_t , который является отдельным идентификатором. Каждый поток в системе, будь то в одном и том же процессе или другой, имеет уникальный TID (или «PID» в терминологии ядра).

То, что считало PID в смысле POSIX «процесса», с другой стороны, называется «идентификатором группы потоков» или «TGID» в ядре. Каждый процесс состоит из одного или нескольких потоков (процессов ядра), каждый из которых имеет свой собственный TID (ядро PID), но все используют один и тот же TGID, который равен TID (ядро PID) исходного потока, в котором выполняется main .

Когда top показывает ваши потоки, он показывает TID (ядро PID), а не PID (ядро TGID), и именно поэтому каждый поток имеет отдельный файл.

С появлением NPTL большинство системных вызовов, которые принимают аргумент PID или действуют на вызывающий процесс, были изменены для обработки PID как TGID и действуют на всю «группу потоков» (процесс POSIX).

1 Brendan [2012-02-06 04:38:00]

Представьте себе какую-то «мета-сущность». Если компания не имеет ни одного из ресурсов (адресного пространства, файловых дескрипторов и т.д.) Своего родителя, то это процесс, и если объект разделяет все ресурсы своего родителя, то это поток. У вас даже может быть что-то наполовину между процессом и потоком (например, некоторые ресурсы разделены, а некоторые не разделены). Взгляните на системный вызов «clone()» (например, http://linux.die.net/man/2/clone), и вы увидите, что Linux делает вещи внутренне.

Теперь скрыть это за какой-то абстракцией, которая делает все похожим на процесс или поток. Если абстракция безупречна, вы никогда не узнаете разницу между «сущностями» и «процессами и потоками». Абстракция не совсем безупречна — PID, который вы видите, на самом деле является «идентификатором объекта».

0 phihag [2012-02-06 04:32:00]

В Linux каждый поток получает идентификатор потока . Идентификатор потока основного потока выполняет двойную функцию как идентификатор процесса (и довольно хорошо известен в пользовательском интерфейсе). Идентификатор потока является деталью реализации Linux и не связан с идентификатором POSIX. Для получения дополнительной информации см. Системный вызов gettid (недоступен с чистого Python с его системной спецификой).

Ссылка на основную публикацию
ВсеИнструменты 220 Вольт
Adblock
detector
×
×