awakeBird Back-end Dev Engineer

Linux基础(四)线程

2018-12-19

该系列文章为《Linux/Unix系统编程手册》的学习笔记,由于该书太过冗长,属于工具书的类别,这里对书中的一些核心内容加以提炼和整理。 书中的编程练习这里不做展示和说明。

概念

线程:允许应用程序并发执行多个任务的一种机制,一个进程可以执行多个线程,这些线程执行相同的程序,共享一份全局内存,包括初始化数据段、未初始化数据段以及堆内存段。进程启动时有且只有单个线程,称为初始(initial)线程或者主(main)线程。

线程ID:线程的唯一标识(C语言中定义为pthread_t),在Linux中,线程ID在所有进程中都是唯一的。

多个线程共享的主要属性有:

  • 进程ID
  • 控制终端
  • 打开的文件描述符
  • 信号处置

线程独占的属性有:

  • 线程id
  • CPU亲和力

线程行为

创建

int pthread_creat(pthread_t *thread, const pthread_addr_t *addr, void *(*start)(void *), void *arg);

线程创建后会从start(arg)开始执行,并继续执行调用之后的语句直到return。与进程类似,创建进程后无法确定那个线程先获得CPU资源。

终止

有四种方式可以终止线程:

  • 线程执行start函数的return正常返回退出。
  • 线程调用pthread_exit()
  • 调用pthread_cancel()取消线程。
  • 任意线程调用exit()或者主线程执行了return语句,会导致进程中所有的线程终止。
void pthread_exit(void *retval);

调用pthread_exit()相当于执行start函数中的return提前返回退出。如果主线程执行pthread_exit(),其他线程将继续运行。

连接

int pthread_join(pthread_t threadm void **retval);

调用pthread_join来等待某个线程终止,如果该线程已经终止,join函数将立即返回,类似于进程的waitpid,不过有两点区别:

  • 线程之间关系是对等的。
  • 无法“连接任意线程”,也无法“以非阻塞方式进行连接”。

僵尸线程:如果线程未分离,则必须使用pthread_join进行连接。如果未进行连接的线程提前终止,这个线程将转变为僵尸线程,与僵尸进程概念类似。如果僵尸线程积累过多,除了浪费资源外,应用将无法创建新的线程。

分离

int pthread_detach(pthread_t thread);

如果不关心线程的返回状态,只是希望系统在线程结束时能够自动清理并移除,可以调用pthread_detach进行分离。分离后的线程不能再被连接。当主线程调用exitreturn后,即使分离过的线程也将立即终止。

取消

int pthread_cancel(pthread_t thread);

调用pthread_cancel会使一个线程立即退出,函数将立即返回而不会等待目标线程退出。

进程与线程

优劣势

线程相比进程的优势在于:

  • 线程数据共享简单。
  • 线程创建的资源消耗小,创建速度比进程快10倍以上。

劣势:

  • 需要保证线程安全。
  • 线程会争用宿主进程的虚拟内存空间(每个线程的线程栈都会消耗进程的虚拟内存)。

在线程中执行进程方法

  • 线程执行exec:任一线程调用exec后调用进程将被完全替换,除了当前线程之外的所有线程都将立即消失。
  • 线程执行fork:线程执行fork后,仅会将当前线程复制到子进程中,其他线程均会在子进程中消失。

线程安全

  • 临界区:访问某一共享变量的代码片段,这段代码片段执行的应为原子操作,同时访问同一共享资源的其他线程不应中断代码的执行。
  • 互斥量:有两个状态,锁定和未锁定,可以被获取(acquire)和释放(release),可以确保共享变量同一时间只有一个线程访问。
  • 条件变量:就共享变量的状态改变通知其他线程,并让其他线程等待这一通知。
  • 线程安全:若函数可以同时供多个线程调用,则称为线程安全函数。

实现线程安全的方式主要有:采用互斥量或使用线程特有数据替代共享变量。可重入函数无需借助互斥量就可实现线程安全。

线程实现模型

线程实现有三种模型,三种模型的不通主要集中在线程如何与内核调度实体(KSE)映射,KSE是内核分配CPU及其他系统资源的基本单位。

多对一:用户级线程

线程创建、调度及同步的所有细节均有进程在用户空间的线程库处理,对于进程中的多个线程,内核一无所知。

优点:

  • 由于不涉及中断,因此线程的创建、终止以及上下文切换、同步等速度都很快。

缺点:

  • 系统调用会阻塞所有的线程。
  • 无法将线程调度给多处理器,无法借助多核优势。

一对一:内核级线程

每个线程映射一个单独的KSE,内核分别对每个线程做调度处理,线程同步操作也通过内核实现。Linux的线程都采用此模型。

当应用程序中包含大量线程时,内核会为每个线程维护一个KSE,给内核调度器造成严重负担,大大降低系统的整体性能。

多对多:两级模型

结合了上述两种模型的有点,每个进程可以有多个KSE,也可以把多个线程映射到同一个KSE。但模型太过复杂,线程调度需要由内核及用户空间共同维护。

(End)


Comments

Content