线程ID与进程ID

进程ID和线程ID

1. 内核标识的线程ID-LWP,在系统级别有效
        在Linux下,目前的线程实现是Native POSIX Thread Libaray,简称NPTL。在这种实现下,线程又被称为轻量级进程。每一个用户态的线程,在内核中都对应一个调度实体,也拥有自己的进程描述符(task_struct结构体)。
        没有线程之前,一个进程对应内核中一个进程描述符,对应一个进程ID。但是引入线程的概念之后,一个用户进程下管辖N个用户态线程。每个线程作为一个独立的调度的实体,在内核态中都有自己的进程描述符,进程和内核描述符一下变成了1:N的关系。POSIX标准又要求同一进程中的线程调用getpid函数时返回相同的进程ID,如何解决上述问题呢?
        Linux内核就引入了线程组的概念:
struct task_struct
  {
    ...                                                                                                                                                               
      pid_t pid;
      pid_t tgid;
      ... 
      struct task_struct* group_leader;
      ... 
      struct list_head thread_group;
      ... 
}

        多线程的进程,又被称为 线程组。线程组内每一个线程在内核中都有一个进程描述符(task_sruct)与之对应,且进程描述符结构体中的pid对应的是线程id,而非进程id。进程描述符中的tgid含义是Thread Group ID,对应的是用户级的进程id。
        现在介绍的线程ID,不同于pthread_t类型的线程ID,和进程ID一样,线程ID是pid_t类型的变量,而且是用来唯一标识线程的一个整型变量。如何看一个线程的ID呢?
如上图所示,我们查看了thread_id进程中的线程相关信息。(最后一行不是)ps命令中的-L选项,会显示如下信息:
(1)LWP:线程ID,即getttid( )系统调用的返回值;
(2)NLWP:线程组内线程的个数

可以看出来,thread_id进程是多线程的,进程ID是6538,进程内有两个线程,线程ID分别是6538、6539。
        Linux提供了gettid系统调用来返回其线程ID,可是glibc并没有将该系统调用封装起来,在开放接口来供程序员使用,如果确实需要获得该线程ID,可采用以下方法:
#include <sys/syscall.h>
pid_t tid;
tid = syscall(SYS_gettid);      

我们用以下方法测试如下代码的线程ID:
//所有线程的pid都是进程的pid,均相同                                                                                                                                    
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/syscall.h>

void* thread_run(void* arg)//传入线程创建的函数
{   
    pid_t tid = syscall(SYS_gettid);
    const char* msg = (const char*)arg;
    printf("%s: %d\n", msg, tid);
    sleep(5);
    
    pthread_exit((void*)0);
}
    
int main()
{   
    pthread_t tid1, tid2, tid3;//无符号长整型
    pthread_create(&tid1, NULL, thread_run, "thread 1");
    pthread_create(&tid2, NULL, thread_run, "thread 2");
    pthread_create(&tid3, NULL, thread_run, "thread 3");
 
    pthread_join(tid1, NULL);//不关心线程退出信息
    pthread_join(tid2, NULL);
    pthread_join(tid3, NULL);
    
    printf("main thread: tid:%x, pid:%d\n", pthread_self(), getpid());
    sleep(5);
    return 0;
}

运行结果为:
可以看到该进程中有一个主线程、三个新线程,打印出来的结果也是四个线程,线程ID分别为6825、6826、6827、6828。结果与我们上面用ps命令查看得到的线程ID一样,所以这里打印的线程ID即为LWP,在内核有效。
        6825标识的即为主线程,在内核中被叫做group leader,内核在创建第一个线程时,会将线程组的ID值设置为第一个线程的线程ID,group leader的指针指向自身,即主线程的进程描述符, 所以线程组内存在一个线程ID与进程ID相等,该线程即为线程组的主线程
        线程组内的其他线程ID则由内核分配,但是线程组ID和主线程的线程组ID一样,无论是主线程创建新线程,还是新线程再创建新线程,都是这样。
        但是,线程与 进程不一样,进程间有父子关系,但是线程之间均为对等关系,即 同一个线程组的线程,没有层次关系

2. 用户级别的线程ID
        pthread_create函数会产生一个线程ID,存放在第一个函数所指向的地址中,这里的线程ID即为用户级别的线程ID。上面说的ID属于进程调度的范畴,因为线程属于轻量级线程,是操作系统调度的最小单位,所以需要一个数值来唯一标识该线程。而pthread_create产生并标记在第一个参数指向的地址中的线程ID,属于NPTL线程库的范畴。线程库的后序操作,就是根据该线程ID来操作线程的,它仅在当前库内有效。
        线程库NPTL提供了pthread_self函数,可获得线程自身的ID:
pthread_t pthread_self(void);           
        对于Linux目前实现的NPTL实现来讲, pthread_t类型的线程ID,本质上是进程地址空间上的一个地址

我们利用pthread_self编写代码查看用户级别线程的ID:
//所有线程的pid都是进程的pid,均相同
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/syscall.h>

void* thread_run(void* arg)//传入线程创建的函数
{
    const char* msg = (const char*)arg;                                                                                                                                     
    //这里打印出tid是地址的形式,是用户级的线程id,只在当前库内有效
    printf("%s: tid:%#x, pid:%d\n", msg, pthread_self(), getpid());
    sleep(5);

    pthread_exit((void*)0);
}

int main()
{
    pthread_t tid1, tid2, tid3;//无符号长整型
    pthread_create(&tid1, NULL, thread_run, "thread 1");
    pthread_create(&tid2, NULL, thread_run, "thread 2");
    pthread_create(&tid3, NULL, thread_run, "thread 3");
    
    pthread_join(tid1, NULL);//不关心线程退出信息
    pthread_join(tid2, NULL);
    pthread_join(tid3, NULL);
    
    printf("main thread: tid:%x, pid:%d\n", pthread_self(), getpid());
    sleep(5);
    return 0;
}

运行结果为:

猜你喜欢

转载自blog.csdn.net/lycorisradiata__/article/details/80380301
id