线程Deep Dive

thread deep dive

update note

  • 2018.07.03 add some figure
  • 2018.07.04 add some practice about tid

0. 调度器

  • 调度器,调度的是task or in fact kernel thread.
  • kernel version < 2.4 : O(n)调度器 调度的时间复杂度与进程数目n相关--> 1个全局链表管理进程,需要遍历/锁链表计算动态/静态优先级.
  • kernel version >= 2.6 : O(1)调度器 单链表 --> 多个优先级链表;使用了很多hardcode的常数
    调度器介绍 - from wowotech
  • CFS调度器...

0.1. process/thread的几个概念

  • 进程:
    • 进程描述符: task struct,其中包含一个mm_struct为该进程的虚拟内存空间.
    • rt进程, 实时进程
    • nice, 进程静态优先级,越小优先级越高 --> 与调度器相关,调度时根据nice值调整分配时间片大小. 进程动态优先级与当前该进程已经跑了几个滴答(tick)相关.
    • cpu affinity, cpu亲和力,该进程上一次时间片在某个core中跑的.由于把该进程调度到该core,l1缓存命中率会较高,cpu亲和力就是衡量该属性的指标.(l1 cache 是每个core独有的,其他l的cache可能会被core共享)
  • 线程:
    • kernel thread的mm_structNULL
    • userspace thread的实现则与平台相关.
    • userspace thread 与 kernel thread 根据不同实现有不同的对应关系,如1:1m:n.
  • 进程与线程的区别:
    • 程序概念上,进程是资源分配(如虚拟内存空间)的单位,线程是cpu调度的单位.
    • 更进一步的区别:1:1模型下的线程与进程

1. Userspace Thread的实现: LinuxThread vs. NPTL

Linux上线程的实现主要有2种,LinuxThread(旧版本)和Redhat的NPTL(Native POSIX Thread Library),2者都分别实现了POSIX Thread标准.
不同平台的pthread引的实现不同.

  • LinuxThread

    • 内核版本 2.0;
    • 使用的系统调用是clone();
    • 存在的问题:
      • 每次新建线程都需clone full process并且需要一个所谓manager thread管理;
      • 资源监控容易令人费解,不区分process和thread,如内存消耗等指示无法确认属于某进程的线程使用多少内存,每个线程的内存占用都是该进程的;
      • 线程的信号处理存在一些问题.Because each thread had its own process ID, asynchronous signals could only be sent to a particular thread, not to the process as a whole.
        • 例如,用户ctrl+z一个进程,只能停止某个特定线程,如果该进程有其他工作线程,将无法停止.
      • getpid()不符合POSIX标准,同一个进程的不同线程将返回不同的pid号.
    • 被加入glibc,并从96年一直沿用到现在.
    • 用户态线程与内核线程的关系: 1:1.
  • NPTL

    • 内核版本 2.5;
    • 使用的系统调用有:clone(),futex()等;
    • 用户态线程与内核线程的关系: 1:1.
    • NPTL的基础是内核版本2.5中Molnar提交的一系列内核patch带来的新特性:
      • clone()添加针对thread生成的优化.Using clone() to spawn a thread is no longer a heavyweight task.
      • 支持thread-specific data areas
      • A new mechanism for tracking threads.
      • 进程作为资源的分配单元,如信号,内存使用等.
      • Fast Userspace Locking (futex)
      • 进程内的线程之间的关系: a parent-child relationship ---> a true peer relationship.
      • fix getpid()
  • 为什么NPTL最后使用1:1的线程模型?
    这里的1:1是指用户线程与内核线程的比例.
    NPTL白皮书的说法是:
    主要从2个角度说明为何采用了1:1

    1. m:n模型应该如何实现,需要有1个用户态的调度器,以便把m个用户线程调度到n个内核线程上,由于内核本身
      有任务调度器,如果该用户态调度器不与内核的任务调度器协同的话,性能会有严重影响(Cache Miss);
      如果该用户态调度器与内核的任务调度器协同,又会带来一些额外开销.
    2. m:n模型能带来什么,可以减少内核线程的数目. 由于2.6版本引入了O(1)调度器,这个减少内核线程
      不会带来太多收益.
  • 关于POSIX标准中的PTHREAD_SCOPE_PROCESS,linux是否不支持呢?

    • 在某个google group中找到了答案,如果单纯只有nptl,那么应该是不支持process_scope的,因为其线程模型是1:1的,
      所有用户线程一起竞争cpu,也就是system_scope.
    • 但提到,可以通过cgroup实现process_scope,做法是将某个process的所有thread绑定到一起.
  • practice

    • top -H中的pid其实是tid
    • pthread_t不合适作为线程标识.原因有2(参见muduo网络库):1. pthread_t可能是个结构体(glibc). 2. pthread_t可能重复.那么线程标识可以用什么呢?gettid()系统调用.该调用返回的是内核任务调度id.
    • 使用gettid系统调用 + thread_local缓存该id如何面对fork:可采用pthread_atfork注册回调.
线程Deep Dive_第1张图片
linux thread vs. NPTL. 主要原因是内核优化了.

NPTL from drdobbs
NPTL white paper
pthread on linux with process_scope

使用pthread

  1. scope不用考虑了,因为linux只支持system_scope
  2. detached or not detached: 这个主要跟业务相关,看业务是否需要线程的退出状态。(大部分工作线程是不需要的,也就是大多数都是用non-detached,即使需要看线程工作退出状态,大多也是自己维护一个线程状态结构)

a simple abstraction of thread

可以参考seastar中对thread的封装.(他本身多了一个业务层面的调度器)

你可能感兴趣的:(线程Deep Dive)