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。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值