Linux 上的线程标识

Posted vector6_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux 上的线程标识相关的知识,希望对你有一定的参考价值。

Linux 上的线程标识

进程PID、线程PID、线程TID

进程PID:进程开启之后,在系统中是唯一的,不可重复的

线程TID:创建一个线程之后,线程有一个标识符,此标识符只在该线程所属的进程上下文才有意义,为pthread_t数据类型。在不同的进程中,可能会出现相同的情况

线程PID:Linux中的POSIX线程库实现的线程其实也是一个进程(LWP),只是该进程与主进程(启动线程的进程)共享一些资源而已,比如代码段,数据段等。在系统中是唯一的,不可重复的

PS : 主线程PID等于线程所在的进程的PID

线程TID (pthread_t)

  • 进程ID是存在于整个系统中的,但线程ID不同,线程ID只有在它所属的进程上下文中才有意义
  • 重点:因为线程ID只在所属的进程上下文才有意义,所以不同的进程创建的不同线程可能具有相同的线程ID
  • TID的类型是: pthread_t,是一个结构体数据类型,所以可移植操作系统实现不能把它作为整数处理

POSIX threads库提供了 pthread_self 函数用于返回当前进程的标识符,其类型为pthread_t

pthread_t 不一定是一个数值类型(整数或指针),也有可能是一个结构体,因此Pthreads专门提供了 pthread_equal 函数用于对比两个线程标识符是否相等

这就带来一系列问题,包括:

  • 无法打印输出 pthread_t ,因为不知道其确切类型。也就没法在日志中用它表示当前线程的id
  • 无法比较 pthread_t 的大小或计算其hash值,因此无法用作关联容器的key
  • 无法定义一个非法的 pthread_t 值,用来表示绝对不可能存在的线程id,因此MutexLock class没有办法有效判断当前线程是否已经持有锁
  • pthread_t 值只在进程内有意义,与操作系统的任务调度之间无法建立有效关联。比方说在/proc文件系统中找不到 pthread_t 对应的task

另外,glibc的Pthreads实现实际上把 pthread_t 用作一个结构体指针 (它的类型是unsigned long),指向一块动态分配的内存,而且这块内存是反复使用的。这就造成 pthread_t 的值很容易重复。Pthreads只保证同一进程之内,同一时刻的各个线程的id不同;不能保证同一进程先后多个线程具有不同的id,更不要说一台机器上多个进程之间的id唯一性了。

例如,下面这段代码中先后两个线程的标识符是相同的:

#include <stdio.h>
#include <pthread.h>
 
void *threadFunc(void*){}
 
int main()
{
    pthread_t t1,t2;
    
    pthread_create(&t1,NULL,threadFunc,NULL); //创建线程
    printf("%lx\\n",t1);                       //打印线程id
    pthread_join(t1,NULL);                    //阻塞等待t1线程结束
 
    pthread_create(&t2,NULL,threadFunc,NULL);
    printf("%lx\\n",t2);
    pthread_join(t2,NULL);
    
    return 0;
}

因此,pthread_t并不适合用作程序中对线程的标识符

线程的PID (pid_t)

在Linux上,建议使用gettid系统调用的返回值作为线程id
这么做的好处有:

  • 它的类型是pid_t,其值通常是一个小整数(最大值是/proc/sys/kernel/pid_max,默认值是32768),便于在日志中输出
  • 在现代Linux中,它直接表示内核的任务调度id,因此在/proc文件系统中可以轻易找到对应项:/proc/tid或/prod/pid/task/tid
  • 在其他系统工具中也容易定位到具体某一个线程,例如在top中我们可以按线程列出任务,然后找出CPU使用率最高的线程id,再根据程序日志判断到底哪一个线程在耗用CPU
  • 任何时刻都是全局唯一的,并且由于Linux分配新pid采用递增轮回办法,短时间内启动的多个线程也会具有不同的线程id
  • 0是非法值,因为操作系统第一个进程init的pid是1

但是glibc并没有封装这个系统调用,需要我们自己实现。封装gettid很简单,但是每次都执行一次系统调用似乎有些浪费,如何才 能做到更高效呢?

  • __thread 变量来缓存gettid的返回值,这样只有在本线程第一次调用的时候才进行系统调用,以后都是直接从 thread local 缓存的线程id拿到结果(这个做法是受了glibc封装getpid()的启发),效率无忧
  • 多线程程序在打日志的时候可以在每一条日志消息中包含当前线程的id,不必担心有效率损失。读者有兴趣的话可以对比一下boost::this_thread::get_id()的实现效率

还有一个小问题,万一程序执行了fork,,那么子进程会不会看到stale的缓存结果呢?解决办法是用 pthread_atfork() 注册一个回调,用于清空缓存的线程id。

以上是关于Linux 上的线程标识的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法在使用 Asp.Net MVC ActionLink、RedirectToAction 等时包含片段标识符?

如何从URL获取片段标识符(hash#之后的值)?

收集有关 Linux 上的线程调度的信息

linux线程的创建、退出、等待、取消、分离

Java 线程转储优先级值与 linux 上的实际线程优先级不对应?

标识符“creal”未定义 - 在 Mac 上可见但在 Linux 上不可见