• Linux上线程ID以及pthread_self()和gettid()函数的使用与对比


    使用背景

    在多进程编程环境下,操作系统会使用进程号来对创建出的进程进行唯一标识进而实现任务调度。那么在多线程编程中也希望能够对创建出的线程进行唯一标识,这样可以将日志信息和线程关联起来,在程序运行崩溃或者CPU占用较高时,可以很快地根据日志信息定位到相应的线程。

    获取方法

    • POXIS标准中Pthread库提供的pthread_self()函数
    • gettid()系统调用

    pthread_self()使用

    使用pthread_create()(函数原型如下)系统调用新建一个线程的时候,pthread_create()函数会修改形参thread指针指向的值,指向新建线程的线程ID,其类型为pthread_t

    #include<pthread.h>
    int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void *(*start)(void *),void *arg);
    

    新建线程在后续运行中如果想要获取自身的线程ID,可以通过Pthread库提供的pthread_self()函数来返回。

    #include<pthread.h>
    int pthread_self()
    

    示例代码

    #include<iostream>
    //使用了pthread,在编译的时候需要连接pthread库
    //编译命令如下:
    //g++ filename.cpp filename.out -pthread
    #include<pthread.h>
    using namespace std;
    pthread_mutex_t mutex;
    void* threadFunc(void* obj){
        pthread_mutex_lock(&mutex);
        cout << "子线程ID为:" << pthread_self() << endl;
        pthread_mutex_unlock(&mutex);
        return nullptr;
    }
    int main(int argc,char* argv[]){
        pthread_t thread;
        pthread_create(&thread,nullptr,&threadFunc,nullptr);
        pthread_mutex_lock(&mutex);
        cout << "新建线程ID为:" << thread << endl;
        pthread_mutex_unlock(&mutex);
        pthread_join(thread,nullptr);
        return 0;
    }
    

    运行截图

    image-20201007144211245

    gettid()使用

    通过查看Linux系统中的man手册,可以得知gettid()相关头文件和函数原型如下:

    #include<sys/types.h>
    pid_t gettid(void)
    

    但在实际的编程中会发现编译时会报错gettid()未声明和定义,这是因为头文件中sys/types.h没有声明该函数同时 glibc中也没有实现。此时需要我们自己使用系统调用封装一个gettid(),函数的封装方式如下:

    #include<syscall.h>
    #include<unistd.h>
    pid_t gettid(){
        return static_cast<pid_t>(syscall(SYS_gettid));
    }
    

    在封装gettid()函数的过程中使用到了syscall()函数和 SYS_gettid常量,下面将简单介绍一下syscall()函数:

    • syscall()是一个小型的库函数,可以将其看作是操作系统所有系统调用入口的集合,该函数可以通过系统调用号和系统调用参数来调用相应的系统调用(SYS_gettid是系统调用号,对gettid()系统调用的唯一标识)。
    • glibc中没有对某些系统调用进行包装时,可以很方便的使用syscall()函数自己进行封装(如上所示)。
    • 如果glibc中有对系统调用的封装,那么优先使用glibc中的系统调用而不建议使用syscall()来进行调用,尤其是需要传参的系统调用。glibc会以适合于架构的方式将参数复制到正确的寄存器,调用者无需关心这些的细节。但是当使用syscall()进行系统调用时,调用者可能需要处理与体系结构有关的详细信息(具体例子请查看man手册)。
    #include<unistd.h>
    long syscall(long number,...);
    

    介绍完syscall()函数,继续回到gettid()函数的使用:

    在单线程的进程中,getpid()函数的值和gettid()的值是相同的。而在多线程中,所有线程的getpid()值都是相同的,每个线程有自己的getpid()值。需要注意的是,不管是单线程还是多线程环境下,主线程的gettid()的值始终和getpid()值相同,可以通过这种方式来判断当前线程是否为主线程。比如:

    bool isMainThread(){
    	return gettid() == getpid();
    }
    

    示例代码

    #include<iostream>
    #include<syscall.h>
    #include<unistd.h>
    #include<pthread.h>
    using namespace std;
    bool isMainThread(){
    	return gettid() == getpid();
    }
    pid_t gettid(){
        return static_cast<pid_t>(syscall(SYS_gettid));
    }
    void* threadFun(void *obj){
        cout << "new thread tid = " << gettid() << endl;
        cout << "new thread is main thread = " << boolalpha << isMainThread() << endl;
        return nullptr;
    }
    int main(int argc,char* argv[]){
        pthread_t thread;
        pthread_create(&thread,nullptr,&threadFun,nullptr);
        pthread_join(thread,nullptr);
        cout << "process pid = " << getpid() << endl;
        cout << "main thread tid = " << gettid() << endl;
        cout << "old thread if main thread = " << isMainThread() << endl;
        return 0;
    }
    

    运行截图

    image-20201007144033784

    pthread_self()与gettid()对比

    pthread_self缺点

    • 无法打印输出pthread_t(上述示例代码中能够用cout打印出pthread_t是因为在Linux的NPTL实现中pthread_t是一个经强制类型转换的无符号长整形指针),POSIX标准中没有明确规定pthread_t的具体类型,可能是int,结构体或者结构体指针。因为不知道确切类型,也就无法在日志中用pthread_t来表示当前线程的id
    • 无法比较pthread_t的大小或者计算其hash值,所以无法作为关联容器的key
    • 无法定义一个非法的pthread_t值,用于表示绝不可能存在的id值。在某些场景下,对象需要线程ID作为成员变量来表示当前对象被哪个线程所持有。如果没有非法的线程ID则无法知晓当前对象是否被线程所持有同时也无法有效判断当前线程是否持有该对象(线程B使用完对象后将线程ID成员变量设置为一个以为不存在的ID,但可能和线程AID重合,线程A就无法真正知道是否持有对象)。
    • pthread_t值只在进程内有意义,与操作系统的任务调度无法建立起有效关联。在/proc文件中无法找到pthread_t对应的任务,这样无法查看某个线程具体的资源耗用情况同时在资源耗用较高时无法定位到相应的线程。
    • 不具备全局唯一性。pthread_t可能是一个结构体指针,指向一块动态分配的内存,而这块内存可能会被反复使用,这样就会造成pthread_t的重复。只能保证同一时刻同一进程不同线程的id不同,不能保证不同时刻各个线程的id不同。

    gettid()好处

    • 返回类型是pid_t,其实明确确定是一个小整数,可以在日志中输出。
    • 0是非法值,操作系统第一个进程initpid是1。这样可以解决上述提出的问题。
    • 在现代Linux中,直接表示内核的任务调度id,可以在proc文件系统中找到对应项:proc idprocpid ask id
    • 程序允许期间任何时候都是全局唯一的,操作系统采用递增轮询的方式分配pid,短时间启动的多个线程会有不同的线程id

    示例代码

    #include<iostream>
    #include<syscall.h>
    #include<unistd.h>
    #include<pthread.h>
    using namespace std;
    pid_t gettid(){
        return static_cast<pid_t>(syscall(SYS_gettid));
    }
    pid_t pid;
    void* threadFun(void *obj){
        pid = gettid();
        return nullptr;
    }
    int main(int argc,char* argv[]){
        pthread_t thread;
        pthread_create(&thread,nullptr,&threadFun,nullptr);
        pthread_join(thread,nullptr);
        cout <<"t1.threadID = "<< thread << " t1.tid = " << pid <<  endl;
    
        pthread_create(&thread,nullptr,&threadFun,nullptr);
        pthread_join(thread,nullptr);
        cout <<"t2.threadID = "<< thread << " t2.tid = " << pid <<  endl;
        return 0;
    }
    

    运行截图

    image-20201007151209969

    参考资料

    • 《Linux多线程服务端编成:使用muduo C++网络库》 —— 陈硕
    • 《A Linux and UNIX System Programming Handbook》—— Michael Kerrisk
  • 相关阅读:
    opencv c++编译
    报bug
    ssh的server安装和安装指定版本的软件的方法
    caffe修改需要的东西 6:40
    caffe修改需要的东西
    leveldb学习:DBimpl
    AndroidStudio加快Gradle速度的方法-android study之旅(103)
    hdu2222--Keywords Search+AC自己主动机模板
    ListView setOnItemClickListener无效原因分析
    Linux打包命令
  • 原文地址:https://www.cnblogs.com/zrcsy/p/13772023.html
Copyright © 2020-2023  润新知