• fork安全的gettid高效实现


    进程有id,可以通过getpid()获得,线程也有id,但是glibc没有提供封装。需要自己发出系统调用。在关键路径,系统调用还是对性能有影响的。因此我们可以想到类似glibc对getpid做的cache化封装,用thread local的方式缓存每个线程的id,每个线程只有第一次调用gettid时才真正发起系统调用。

    #include <stdio.h>
    #include <syscall.h>
    #include <unistd.h>
    
    pid_t gettid() {
        static __thread pid_t cached_tid;
        if (cached_tid == 0) {
            cached_tid = syscall(SYS_gettid);
        }
        return cached_tid;
    }

    这段代码运行的很好,直到遇到fork。在我看来,fork是单线程时代的东西,与多线程格格不入,所以我们的代码中很少用到。其实这个问题除了对调用fork的线程来说是诡异的,因为fork时,其他线程是不会被fork的。但是对于主线程或者单线程程序,这个问题也还是存在的,存在就不爽。

    或许可以想到用pthread_atfork来做这个事情,其原型是这样的。

    int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));

    文档说,child会在子进程中被调用。但是这里有个问题,cached_tid是线程局部变量,每个线程里的地址是不一样的,而child函数不支持传地址,难道我们要用动态生成代码(thunk)的猥琐方式?

    其实不必那么复杂,虽然glibc缓存了getpid,但是fork后getpid是会变的,因此我们可以在每次调用时再多比较一下pid是否发生了变化:

    pid_t gettid() {
        static __thread pid_t cached_pid;
        static __thread pid_t cached_tid;
        pid_t pid = getpid();
        if (cached_pid != pid || cached_tid == 0) {
            cached_pid == pid;
            cached_tid = syscall(SYS_gettid);
        }
        return cached_tid;
    }

    getpid是高效的,因此这段代码也是高校的。

    查阅glibc的源代码,getpid是从当前线程控制块里读取的,其实里面也有线程id,但是glibc却迟迟不肯提供封装,让我想起了一句话,程序员何苦为难程序员。

    测一下:

    int main() {
        printf("Parent: pid=%d, tid=%d
    ", getpid(), gettid());
        if (fork() == 0) {
            printf("Child: pid=%d, tid=%d
    ", getpid(), gettid());
        } else {
            printf("Parent after fork: pid=%d, tid=%d
    ", getpid(), gettid());
        }
    }

    Parent: pid=10776, tid=10776
    Parent after fork: pid=10776, tid=10776
    Child: pid=10777, tid=10777

    行为符合预期。

  • 相关阅读:
    程序员修炼之道阅读笔记2
    程序员修炼之道阅读笔记1
    软件体系架构的质量属性
    计算贴现率相关问题
    以《淘宝网》为例,描绘质量属性的六个常见属性场景
    第十四周总结
    软件需求模式阅读笔记
    第十三周总结
    第十二周总结
    重大技术需求进度报告六
  • 原文地址:https://www.cnblogs.com/chen3feng/p/4139730.html
Copyright © 2020-2023  润新知