Реализованы ли потоки как процессы в Linux?

$ echo 3.14 >> /tmp/numbers
$ echo 2 >> /tmp/numbers
$ echo 4.0 >> /tmp/numbers

$ awk '{print $1 * $1}' < /tmp/numbers
9.8596
4
16
$ awk '{print $1 * $1}' < /tmp/numbers > /path/to/OutputFile
65
20.03.2018, 15:12
7 ответов

Я думаю, что эта часть справочной страницы clone(2) может прояснить разницу. PID:

CLONE_THREAD (начиная с Linux 2.4.0-test8)
Если CLONE_THREAD установлен, дочерний элемент помещается в тот же поток group в качестве вызывающего процесса.
Группы потоков были добавлены в Linux 2.4 для поддержки Потоки POSIX — это набор потоков, которые совместно используют один ПИД. Внутренне этот общий PID является так называемым потоком. идентификатор группы (TGID) для группы потоков. Начиная с Linux 2.4 вызовы getpid(2) возвращают TGID вызывающей стороны.

Фраза «потоки реализованы как процессы» относится к проблеме, связанной с тем, что в прошлом у потоков были отдельные идентификаторы PID. По сути, в Linux изначально не было потоков внутри процесса, а были только отдельные процессы (с отдельными PID), которые могли иметь некоторые общие ресурсы, такие как виртуальная память или файловые дескрипторы. CLONE_THREAD и разделение идентификатора процесса(*) и идентификатора потока делают поведение Linux более похожим на другие системы и больше на требования POSIX в этом смысле. Хотя технически ОС по-прежнему не имеет отдельных реализаций для потоков и процессов.

Обработка сигналов была еще одной проблемной областью в старой реализации, это описано более подробно в статье. @FooF ссылается на в своем ответе.

Как отмечено в комментариях, Linux 2.4 также был выпущен в 2001 году, в том же году, что и книга, так что неудивительно, что новости не попали в эту печать.

58
27.01.2020, 19:32

Вы правы, действительно "что-то должно было измениться между 2001 годом и настоящим". Книга, которую вы читаете, описывает мир в соответствии с первой исторической реализацией потоков POSIX в Linux, которая называется LinuxThreads (см. также статью Википедии).

У LinuxThreads были некоторые проблемы совместимости со стандартом POSIX — например, потоки, не использующие общие PID — и некоторые другие серьезные проблемы. Чтобы исправить эти недостатки, Red Hat инициировала другую реализацию под названием NPTL (собственная библиотека потоков POSIX), чтобы добавить необходимую поддержку библиотеки ядра и пользовательского пространства для достижения лучшего соответствия POSIX (взяв хорошие части из еще одного конкурирующего проекта повторной реализации IBM под названием NGPT (" Потоки Posix следующего поколения"), см. статью Википедии о NPTL). Дополнительные флаги, добавленные к системному вызову clone(2) (особенно CLONE_THREAD, на которые @ikkkachu указывает в своем ответе), вероятно, наиболее очевидная часть модификаций ядра. Часть работы, связанная с пользовательским пространством, в конечном итоге была включена в библиотеку GNU C.

В настоящее время некоторые встроенные Linux SDK используют старую реализацию LinuxThreads, потому что они используют версию LibC с меньшим объемом памяти, называемую uClibc (также называемую µClibc), и потребовалось значительное количество лет, прежде чем пользовательское пространство NPTL реализация из GNU LibC была перенесена и принята в качестве реализации потоковой передачи POSIX по умолчанию, поскольку, вообще говоря, эти специальные платформы не стремятся молниеносно следовать новейшим модам. Это можно наблюдать, заметив, что действительно PID для разных потоков на этих платформах также отличаются, в отличие от стандарта POSIX, - как описано в книге, которую вы читаете. На самом деле, как только вы вызвали pthread_create(), вы внезапно увеличили количество процессов с одного до трех, так как для сохранения беспорядка потребовался дополнительный процесс.

На странице руководства Linux pthreads(7) представлен исчерпывающий и интересный обзор различий между ними. Другим информативным, хотя и устаревшим, описанием различий является эта статья Ульриха Деппера и Инго Молнара о конструкции NPTL.

Я рекомендую вам не воспринимать эту часть книги слишком серьезно. Вместо этого я рекомендую Butenhof Programming POSIX threads и справочные страницы POSIX и Linux по этому вопросу. Многие учебники по этому вопросу неточны.

44
27.01.2020, 19:32

В принципе, информация в вашей книге исторически точна из-за постыдно плохой истории реализации потоков в Linux. Этот мой ответ на связанный вопрос о SO также служит ответом на ваш вопрос:

https://stackoverflow.com/questions/9154671/distinction-between-processes-and-threads-in-linux/9154725# 9154725

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

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

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

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

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

8
27.01.2020, 19:32

Когда дело доходит до Linux, процессы и потоки по сути одно и то же. То есть они создаются с помощью одного и того же системного вызова: clone.

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

Что сближает потоки и объекты в Linux, так это системный вызов unshare. Объекты ядра, которые изначально являются общими, могут быть отменены после создания потока. Таким образом, вы можете, например, иметь два потока одного и того же процесса, которые имеют разное пространство файловых дескрипторов (отменив совместное использование файловых дескрипторов после создания потоков). Вы можете проверить это самостоятельно, создав поток, вызвав unshare в обоих потоках, а затем закрыв все файлы и открыв новые файлы, каналы или объекты в обоих потоках. Затем загляните в /proc/your_proc_fd/task/*/fd и вы увидите, что каждая задача (которую вы создали как поток) будет иметь разные fd.

На самом деле, как создание новых потоков, так и новых процессов являются библиотечными подпрограммами, которые вызывают clone и указывают, какой из объектов ядра является вновь созданным процессом-потоком-штукой amajig (т.е. task ) будет использоваться совместно с вызывающим процессом/потоком.

4
27.01.2020, 19:32

Внутренне в ядре linux нет процессов или потоков. Процессы и потоки - это в основном концепция пользовательского пространства, само ядро ​​​​видит только «задачи», которые представляют собой планируемый объект, который может не использовать ни один, некоторые или все свои ресурсы с другими задачами. Потоки — это задачи, которые были настроены на совместное использование большей части своих ресурсов (адресное пространство, mmaps, каналы, обработчики открытых файлов, сокеты и т. д.) с родительской задачей, а процессы — это задачи, которые были настроены на совместное использование минимальных ресурсов с родительской задачей. .

Когда вы используете Linux API напрямую (clone() вместо fork() и pthread_create()), у вас гораздо больше гибкости. в определении того, сколько ресурсов для общего или не совместного использования, и вы можете создавать задачи, которые не являются ни полностью процессом, ни полностью потоком. Если вы используете эти низкоуровневые вызовы напрямую, также можно создать задачу с новым TGID (таким образом, рассматриваемую как процесс большинством пользовательских инструментов), которая фактически делит все свои ресурсы с родительской задачей, или наоборот, для создания задача с общим TGID (поэтому рассматривается как поток большинством пользовательских инструментов), которая не использует ресурсы совместно со своей родительской задачей.

В то время как Linux 2.4 реализует TGID, это в основном только для учета ресурсов. Многие пользователи и инструменты пользовательского пространства считают полезной возможность группировать связанные задачи вместе и вместе сообщать об использовании своих ресурсов.

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

12
27.01.2020, 19:32

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

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

Похоже, что в последних ядрах был отдельный TID, который можно запрашивать, именно он отличается для потоков, подходящий фрагмент кода, чтобы показать это в каждой из функций main() thread_function() выше:

    long tid = syscall(SYS_gettid);
    printf("%ld\n", tid);

Таким образом, весь код с этим будет следующим:

#include <pthread.h>                                                                                                                                          
#include <stdio.h>                                                                                                                                            
#include <unistd.h>                                                                                                                                           
#include <syscall.h>                                                                                                                                          

void* thread_function (void* arg)                                                                                                                             
{                                                                                                                                                             
    long tid = syscall(SYS_gettid);                                                                                                                           
    printf("child thread TID is %ld\n", tid);                                                                                                                 
    fprintf (stderr, "child thread pid is %d\n", (int) getpid ());                                                                                            
    /* Spin forever. */                                                                                                                                       
    while (1);                                                                                                                                                
    return NULL;                                                                                                                                              
}                                                                                                                                                             

int main ()                                                                                                                                                   
{                                                                                                                                               
    pthread_t thread;                                                                               
    long tid = syscall(SYS_gettid);     
    printf("main TID is %ld\n", tid);                                                                                             
    fprintf (stderr, "main thread pid is %d\n", (int) getpid ());                                                    
    pthread_create (&thread, NULL, &thread_function, NULL);                                           
    /* Spin forever. */                                                                                                                                       
    while (1);                                                                                                                                                
    return 0;                                                                                                                                                 
} 

Пример вывода:

main TID is 17963
main thread pid is 17963
thread TID is 17964
child thread pid is 17963
23
27.01.2020, 19:32

Линус Торвальдс заявил в сообщении списка рассылки ядра в 1996 году, что «и потоки, и процессы рассматриваются как« контекст выполнения »», который является «просто конгломератом всех состояний этого CoE.... включает такие вещи, как состояние процессора, состояние MMU, разрешения и различные состояния связи (открытые файлы, обработчики сигналов и т. д. )».

// simple program to create threads that simply sleep
// compile in debian jessie with apt-get install build-essential
// and then g++ -O4 -Wall -std=c++0x -pthread threads2.cpp -o threads2
#include <string>
#include <iostream>
#include <thread>
#include <chrono>

// how many seconds will the threads sleep for?
#define SLEEPTIME 100
// how many threads should I start?
#define NUM_THREADS 25

using namespace std;

// The function we want to execute on the new thread.
void threadSleeper(int threadid){
    // output what number thread we've created
    cout << "task: " << threadid << "\n";
    // take a nap and sleep for a while
    std::this_thread::sleep_for(std::chrono::seconds(SLEEPTIME));
}

void main(){
    // create an array of thread handles
    thread threadArr[NUM_THREADS];
    for(int i=0;i<NUM_THREADS;i++){
        // spawn the threads
        threadArr[i]=thread(threadSleeper, i);
    }
    for(int i=0;i<NUM_THREADS;i++){
        // wait for the threads to finish
        threadArr[i].join();
    }
    // program done
    cout << "Done\n";
    return;
}

Как вы видите, эта программа создаст одновременно 25 потоков, каждый из которых приостановится на 100 секунд, а затем снова присоединится к основной программе. После того, как все 25 потоков присоединятся к программе, программа завершится и завершится.

С помощью topвы сможете увидеть 25 экземпляров программы threads2. Но чертовски скучно. Вывод ps auwxеще менее интересен... НО ps -eLfстановится довольно захватывающим.

UID        PID  PPID   LWP  C NLWP STIME TTY          TIME CMD
debian     689   687   689  0    1 14:52 ?        00:00:00 sshd: debian@pts/0  
debian     690   689   690  0    1 14:52 pts/0    00:00:00 -bash
debian    6217   690  6217  0    1 15:04 pts/0    00:00:00 screen
debian    6218  6217  6218  0    1 15:04 ?        00:00:00 SCREEN
debian    6219  6218  6219  0    1 15:04 pts/1    00:00:00 /bin/bash
debian    6226  6218  6226  0    1 15:04 pts/2    00:00:00 /bin/bash
debian    6232  6219  6232  0   26 15:04 pts/1    00:00:00./threads2
debian    6232  6219  6233  0   26 15:04 pts/1    00:00:00./threads2
debian    6232  6219  6234  0   26 15:04 pts/1    00:00:00./threads2
debian    6232  6219  6235  0   26 15:04 pts/1    00:00:00./threads2
debian    6232  6219  6236  0   26 15:04 pts/1    00:00:00./threads2
debian    6232  6219  6237  0   26 15:04 pts/1    00:00:00./threads2
debian    6232  6219  6238  0   26 15:04 pts/1    00:00:00./threads2
debian    6232  6219  6239  0   26 15:04 pts/1    00:00:00./threads2
debian    6232  6219  6240  0   26 15:04 pts/1    00:00:00./threads2
debian    6232  6219  6241  0   26 15:04 pts/1    00:00:00./threads2
debian    6232  6219  6242  0   26 15:04 pts/1    00:00:00./threads2
debian    6232  6219  6243  0   26 15:04 pts/1    00:00:00./threads2
debian    6232  6219  6244  0   26 15:04 pts/1    00:00:00./threads2
debian    6232  6219  6245  0   26 15:04 pts/1    00:00:00./threads2
debian    6232  6219  6246  0   26 15:04 pts/1    00:00:00./threads2
debian    6232  6219  6247  0   26 15:04 pts/1    00:00:00./threads2
debian    6232  6219  6248  0   26 15:04 pts/1    00:00:00./threads2
debian    6232  6219  6249  0   26 15:04 pts/1    00:00:00./threads2
debian    6232  6219  6250  0   26 15:04 pts/1    00:00:00./threads2
debian    6232  6219  6251  0   26 15:04 pts/1    00:00:00./threads2
debian    6232  6219  6252  0   26 15:04 pts/1    00:00:00./threads2
debian    6232  6219  6253  0   26 15:04 pts/1    00:00:00./threads2
debian    6232  6219  6254  0   26 15:04 pts/1    00:00:00./threads2
debian    6232  6219  6255  0   26 15:04 pts/1    00:00:00./threads2
debian    6232  6219  6256  0   26 15:04 pts/1    00:00:00./threads2
debian    6232  6219  6257  0   26 15:04 pts/1    00:00:00./threads2
debian    6260  6226  6260  0    1 15:04 pts/2    00:00:00 ps -eLf

Здесь вы можете увидеть все 26 ЦП, созданных программой thread2. Все они имеют один и тот же идентификатор процесса (PID )и идентификатор родительского процесса (PPID ), но у каждого из них свой идентификатор LWP (, облегченный процесс )и количество LWP (. ] NLWP )указывает, что существует 26 CoE — основная программа и 25 порожденных ею потоков.

6
27.01.2020, 19:32

Теги

Похожие вопросы