【问题标题】:Distinction between processes and threads in LinuxLinux中进程和线程的区别
【发布时间】:2015-06-08 15:34:30
【问题描述】:

在阅读了 Robert Love 的 this answer 和“Linux 内核开发”以及随后的 clone() 系统调用后,我发现 Linux 中的进程和线程(几乎)与内核无法区分。它们之间有一些调整(在引用的 SO 问题中讨论为“更多共享”或“更少共享”),但我仍有一些问题需要回答。

我最近在一个涉及几个 POSIX 线程的程序上工作,并决定在这个前提下进行试验。在创建两个线程的进程中,所有线程当然会获得由pthread_self() 返回的唯一值,然而,而不是getpid()

我创建的示例程序如下:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <pthread.h>

void* threadMethod(void* arg)
{
    int intArg = (int) *((int*) arg);

    int32_t pid = getpid();
    uint64_t pti = pthread_self();

    printf("[Thread %d] getpid() = %d\n", intArg, pid);
    printf("[Thread %d] pthread_self() = %lu\n", intArg, pti);
}

int main()
{
    pthread_t threads[2];

    int thread1 = 1;

    if ((pthread_create(&threads[0], NULL, threadMethod, (void*) &thread1))
         != 0)
    {
        fprintf(stderr, "pthread_create: error\n");
        exit(EXIT_FAILURE);
    }

    int thread2 = 2;

    if ((pthread_create(&threads[1], NULL, threadMethod, (void*) &thread2))
         != 0)
    {
        fprintf(stderr, "pthread_create: error\n");
        exit(EXIT_FAILURE);
    }

    int32_t pid = getpid();
    uint64_t pti = pthread_self();

    printf("[Process] getpid() = %d\n", pid);
    printf("[Process] pthread_self() = %lu\n", pti);

    if ((pthread_join(threads[0], NULL)) != 0)
    {
        fprintf(stderr, "Could not join thread 1\n");
        exit(EXIT_FAILURE);
    }

    if ((pthread_join(threads[1], NULL)) != 0)
    {
        fprintf(stderr, "Could not join thread 2\n");
        exit(EXIT_FAILURE);
    }

    return 0;
}

(这是在 64 位 Fedora 上编译的 [gcc -pthread -o thread_test thread_test.c];由于 pthread_t 使用的 64 位类型源自 &lt;bits/pthreadtypes.h&gt;,因此代码需要稍作更改才能在 32 位版本上编译。 )

我得到的输出如下:

[bean@fedora ~]$ ./thread_test 
[Process] getpid() = 28549
[Process] pthread_self() = 140050170017568
[Thread 2] getpid() = 28549
[Thread 2] pthread_self() = 140050161620736
[Thread 1] getpid() = 28549
[Thread 1] pthread_self() = 140050170013440
[bean@fedora ~]$ 

通过在gdb 中使用调度程序锁定,我可以保持程序及其线程处于活动状态,这样我就可以捕获top 所说的内容,仅显示进程

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
28602 bean      20   0 15272 1112  820 R  0.4  0.0   0:00.63 top
 2036 bean      20   0  108m 1868 1412 S  0.0  0.0   0:00.11 bash
28547 bean      20   0  231m  16m 7676 S  0.0  0.4   0:01.56 gdb
28549 bean      20   0 22688  340  248 t  0.0  0.0   0:00.26 thread_test
28561 bean      20   0  107m 1712 1356 S  0.0  0.0   0:00.07 bash

当显示线程时,说:

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
28617 bean      20   0 15272 1116  820 R 47.2  0.0   0:00.08 top
 2036 bean      20   0  108m 1868 1412 S  0.0  0.0   0:00.11 bash
28547 bean      20   0  231m  16m 7676 S  0.0  0.4   0:01.56 gdb
28549 bean      20   0 22688  340  248 t  0.0  0.0   0:00.26 thread_test
28552 bean      20   0 22688  340  248 t  0.0  0.0   0:00.00 thread_test
28553 bean      20   0 22688  340  248 t  0.0  0.0   0:00.00 thread_test
28561 bean      20   0  107m 1860 1432 S  0.0  0.0   0:00.08 bash

似乎很清楚,程序,或者可能是内核,与进程相比,有一种独特的方式来定义线程。根据top,每个线程都有自己的PID - 为什么?

【问题讨论】:

  • clone() 正是 Linux 实现线程和fork() 的方式。重要的是,与 PID 交谈会将信号传递给需要了解的每个人。如果内核为线程分配额外的 ID,那不关你的事,也不会影响你与进程的对话方式。
  • 好的link 可以通过。
  • "Linux 中的进程和线程(几乎)与内核无法区分" 嗯,这不是真的。关于 Linux 内核是如何工作的,对于进程和线程来说,几乎没有什么可以说的。拥有 vm 的视图?只有进程。可以安排吗?只有线程。有文件描述符表吗?只有进程。有优先权吗?只有线程。以此类推。

标签: c linux multithreading


【解决方案1】:

这些混淆都源于内核开发人员最初持有一个不合理和错误的观点,即线程几乎可以完全在用户空间中使用内核进程作为原语来实现,只要内核提供一种使它们共享内存的方法和文件描述符。这导致了臭名昭著的 POSIX 线程的 LinuxThreads 实现糟糕,这是用词不当,因为它没有提供任何与 POSIX 线程语义相近的东西。最终 LinuxThreads 被替换(被 NPTL),但许多令人困惑的术语和误解仍然存在。

首先要意识到,“PID”在内核空间和用户空间中意味着不同的东西。内核所称的 PID 实际上是内核级别的线程 ID(通常称为 TID),不要与 pthread_t 混淆,后者是一个单独的标识符。系统上的每个线程,无论是在同一个进程中还是在不同的进程中,都有一个唯一的 TID(或内核术语中的“PID”)。

另一方面,POSIX 意义上的“进程”被认为是 PID,在内核中被称为“线程组 ID”或“TGID”。每个进程由一个或多个线程(内核进程)组成,每个线程都有自己的 TID(内核 PID),但都共享相同的 TGID,该 TGID 等于运行main 的初始线程的 TID(内核 PID)。

top 向您显示线程时,它显示的是 TID(内核 PID),而不是 PID(内核 TGID),这就是为什么每个线程都有一个单独的线程。

随着 NPTL 的出现,大多数采用 PID 参数或作用于调用进程的系统调用已更改为将 PID 视为 TGID 并作用于整个“线程组”(POSIX过程)。

【讨论】:

  • 感谢您的回答,毫无疑问,我会研究您所说的内容一段时间。也许今天我的帖子中最大的问题(据我所知)是,“为什么其他人没有问这个问题?” (至少,通过一个容易获得的资源。)当然,对于像我这样涉及多线程应用程序的人来说,这一定是一个大话题?
  • 现在(现在我们已经摆脱了 LinuxThreads 的惨败)应用程序员真的可以按照 POSIX 的规定使用 POSIX 线程 开始工作,而不必过多担心什么在引擎盖下继续进行,因为一切都正常工作。我怀疑这就是实现细节不再受到太多关注的原因。 BTW man 7 pthreads 对其工作原理有一些基本解释。
  • @bean - 这里已经从许多不同的角度提出了许多不同的问题。 R 给出了一个特别好的答案,因为它从 linux 历史和技术的角度触及了几个层次的混淆。
  • 谢谢。 “当 top 显示线程时,它显示的是 TID(内核 PID),而不是 PID(内核 TGID),这就是为什么每个线程都有一个单独的线程。”现在还是这样吗?我输入了top -H,但无法弄清楚。
【解决方案2】:

想象某种“元实体”。如果实体不共享其父级的任何资源(地址空间、文件描述符等),那么它就是一个进程,如果该实体共享其父级的所有资源,那么它就是一个线程。你甚至可以在进程和线程之间有一些中间的东西(例如,一些资源共享而一些不共享)。看看“clone()”系统调用(例如http://linux.die.net/man/2/clone),你会发现 Linux 内部是这样处理的。

现在将其隐藏在某种抽象之后,使一切看起来像一个进程或一个线程。如果抽象是完美的,你永远不会知道“实体”和“进程和线程”之间的区别。不过,抽象并不是完美无缺的——您看到的 PID 实际上是一个“实体 ID”。

【讨论】:

  • 抱歉,您的回答根本没有说明我的问题。我已经看过clone() 的手册页。进程和线程之间存在抽象的事实让我首先提出这个问题。很容易说,因为我选择调用 clone() 时共享的资源少于 50%,所以我应该得到一个 线程 而不是进程,同样地。
【解决方案3】:

在 Linux 上,每个线程都有一个线程 ID。主线程的线程 ID 充当进程 ID 的双重职责(并且在用户界面中相当有名)。线程 ID 是 Linux 的一个实现细节,与 POSIX ID 无关。有关更多详细信息,请参阅gettid 系统调用(纯 Python 不可用,因为它是特定于系统的)。

【讨论】:

  • 我不确定The thread ID of the main thread serves double duty as the process ID 是否属实。当我运行上面的代码时,主线程的线程 ID 与 PID 不同。
猜你喜欢
  • 1970-01-01
  • 2016-09-22
  • 2023-01-02
  • 2010-09-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多