Skip to content

Thread

What is Thread

线程是独立的指令流,也可以被kernel调度运行

一个进程里的线程是共享该进程的global data、heap、code,但是不共享stack、register set、program counter (如果线程不单独拥有这些资源,就无法单独被调度执行);对于共享资源的访问是同步的

线程是操作系统调度的最小单元:

  1. 每一个线程有自己独立的控制流
  2. 每一个线程都能处于任何一种调度状态

不管有多少个CPU core,线程的存在能提高CPU的性能和吞吐量;核心就是因为它能被单独调度,同时共享了进程资源。

Thread Benefits

  1. Responsiveness

    multithreading an interactive application allows a program to continue running even part of it is blocked or performing a lengthy operation

  2. Resource sharing

    sharing resources may result in efficient communication and high degree of cooperation. Threads share the resources and memory of the process by default.

  3. Economy

    thread is more lightweight than processes: create and context switch

  4. Scalability

    better utilization of multiprocessor architectures: running in parallel

image-20231207162324568

  • 并发Concurrency:不强调同一个时刻每一个线程都在运行

    即使没有multi-core,也可以在一个CPU上以时间片轮转的方式执行

    有multi-core,就真正有可能同时运行

  • 在设计时,就尽可能切分成多个时间片

  • is about structure
  • 并行Parallelism:同一个时刻在不同CPU上真正run着不同的线程

只有一个CPU-core是不可能实现并行的

  • is about execution

Implementing Threads

线程既可以在用户态被实现,也可以在内核态被实现

  1. user thread:线程实现在内核之上,不受内核支持
  2. kernel thread:线程由内核直接支持和管理

内核级线程 kernel-level Threads

  1. 内核知道线程的存在,当一个线程被block,另一个线程还可以被调度;否则一个线程被block之后,该进程就没办法被调度了
  2. 创建需要系统调用,cost比较大,需要TCB (thread control block)

用户态线程 user-level Threads

用户态线程和内核态线程的关系

  1. many-to-one

    image-20231207165646507

  2. one-to-one:一个用户态线程映射到一个内核态线程上

    允许一个线程block时其它线程运行

    多线程可以在多处理器上并发运行

    创建一个用户线程需要创建一个相应的内核线程

    操作系统限制了线程的数量,到达一定数量之后会被kill掉

  3. many-to-many:多个用户态线程映射到多个内核态线程上

    可以根据需要创建尽可能多的线程

    中间有一个桥梁叫做LWP,OS调度的时候,是从LWP中选一段,再由LWP决定调度哪个线程

  4. two-level model:类似于多对多,但它允许一个用户态线程绑定到一个内核态线程上

Threading Issues

Semantics of fork and exec system calls

  1. Fork duplicates the whole single-threaded process
  2. Exec typically replaces the entire process, multithreaded or not

Signal handling

  1. 消息处理函数都有一个回调函数,当某个事件发生的时候,会执行回调函数
  2. 如果不自己注册回调函数,操作系统会分配一个默认的回调函数
  3. 一旦消息被delivered了,就必须被处理
  4. 但是让哪一个线程来运行回调函数:

    可以指定一个线程运行,也可以从当前正在运行的线程中选

  5. 信号处理可以是同步的,也可以是异步的

Thread cancellation of target thread

  1. asynchronous cancellation:立即终止目标线程
  2. deferred cancellation:目标线程周期性地检查它是否应该被终止

Thread-specific data

  1. Thread-local storage (线程本地存储TLS):允许每一个线程有它自己的数据拷贝

    有一个修饰符_TLS,编译器能识别这个修饰符,会在另外一个地方,开一个array,操作系统能拿到当前线程的id (即tid),之后在线程中用到这个数据时,array[tid]

  2. local变量只能在当前函数调用时时生效,但tls变量是全局性的,类似于static data

  3. TLS is unique to each thread

  4. 虽然加上tls之后,每个线程都操纵自己的拷贝,但他们在物理内存上不是隔离的

    可以把其他线程的栈的虚拟地址拿过来,甚至线程1可以改变线程2的行为,线程和线程之间是没办法隔离的

Scheduler activations

  1. Lightweight Process (LWP)在多对多、两级模型上存在

    内核调度的是内核线程,thread library管理用户线程,用户线程放在LWP上

    在一对一的情况下,LWP指的是内核线程,线程指的是用户态线程

Linux Threads

Linux提供了fork和clone的系统调用

通过clone可以创建出来既不是线程又不是进程的东西,又能让它共享memory space,又不共享file descriptor

clone()提供了很多flag,规定了新创建的执行实体 (称为task),和原先的task共享哪些memory

image-20231211143159850

image-20231211153412514

FS/VM/SIGHAND/FILES -> equivalent to thread creation

no flag set, no sharing -> equivalent to fork

part of flags set -> uses term task

e.g.:

如果CLONE_FILES打开,在新的task中,就不必再打开已打开的文件,可以直接使用fd

而且子任务可以关掉父任务打开的文件

Pthreads

进程fork后,fork所在的那一条线程会会继续执行。如果fork()之后,子进程继续向下执行,有创建线程的话,子进程会创建线程;如果fork()之后,子进程继续向下执行,没有创建线程的话,子进程就不会创建线程;fork在线程函数里的话,只会把线程复制一份。

不管怎么样,fork都复制了整个进程,但是只启用了它所在的那个执行路径,如果它所在的那个执行路径fork后有创建线程的话,子进程会创建线程;如果它所在的那个执行路径fork后没有创建线程而在fork之前有创建线程的话,那么fork不会启用fork之前创建的线程,只会启用当前所在线程,继续执行下去。

https://blog.csdn.net/yi_chengyu/article/details/120424744

Dissecting Linux Threads

创建新的task的时候,栈空间是从已经存在的task的堆空间中划分出来的

栈在哪里无所谓,只要给它分配一段空间,告诉操作系统这是它的栈就可以了

栈只是一块memory,不存在硬件栈(即使有的CPU真的有硬件栈,也不是给用户用的)

COW机制

只有在写入buffer的时候才进行拷贝操作,在clone的时候不拷贝memory

__thread

__thread int x;

加上这个修饰词之后,每个线程都有各自的x,它们操作x的时候互不干扰

不加这个修饰词,x就是一个全局变量,每个线程操作x的时候会互相干扰