Linux多线程编程
Linux多线程编程
- 创建线程和结束线程
- 读取和设置线程属性
- POSIX线程同步方式:POSIX信号量、互斥锁和条件变量
Linux线程概述
线程模型
线程是程序中完成一个独立任务的完整执行序列,即一个可调度实体。根据运行环境和调度者的身份,线程可分为内核线程和用户线程。内核线程在有的系统上也称为LWP(轻量级线程),运行在内核空间,由内核调度。用户线程运行在用户空间,由线程库来调度。当进程的一个内核线程获得CPU的使用权时,它就加载并运行一个用户线程。可见,内核线程相当于用户线程运行的容器。一个进程可以拥有M个内核线程和N个用户线程,其中M≤N。并且在一个系统的所有进程中,M和N的比值都是固定的。按照M:N的取值,线程的实现方式可分为三种模式:
完全在用户空间实现
完全由内和调度
双层调度
完全在用户空间实现的线程无需内核的支持,内核甚至根本不知道这些线程的存在。线程库负责管理所有执行线程,比如线程的优先级、时间片等。线程库利用longjmp来切换线程的执行,使它们看起来像是并发执行的。但实际上内核仍然是把整个进程作为最小单位来调度的。换句话说,一个进程的所有执行线程共享该进程的时间片,它们对外表现出相同的优先级。因此,对这种实现方式而言,N=1,即M个用户空间线程对于1个内核线程,而该内核线程实际上就是进程本身。完全在用户空间实现的线程的优点是:创建和调度线程都无须内核的干预,因此速度相当快。并且由于它不占用额外的内核资源,所以即使一个进程创建了很多线程,也不会对系统性能造成明显的影响。其缺点是,对于多处理器系统,一个进程的多个线程无法运行在不同的CPU上,因为内核是按照其最小调度单位来分配CPU的。此外,线程的优先级只对同一个进程中的线程有效,比较不同进程中的线程的优先级没有意义。
完全由内核调度的模式将创建、调度线程的任务都交给了内核,运行在用户空间的线程无需执行管理任务,这与完全在用户空间实现的线程恰恰相反。完全由内核调度的这种线程实现方式满足M:N=1:1,即1个用户空间线程被映射为1个内核线程。
双层调度模式是前两种实现模式的混合体:内核调度M个内核线程,线程库调度N个用户线程。这种线程实现方式结合了前两种方式的优点:不但不会消耗过多的内核资源,而且线程切换速度也较快,同时可以充分利用多处理器的优势。
Linux线程库
自Linux2.6版本开始,提供了真正的内核线程,新的NPTL线程库也应运而生,相比LinuxThreads,NPTL的主要优势在于:
- 内核线程不再是一个进程,因此避免了许多用进程模拟线程导致的语义问题。
- 摒弃了管理线程,终止线程,回收线程堆栈等工作都可以由内核来完成
- 由于不存在管理线程,所以一个进程的线程可以运行在不同的CPU上,从而充分利用了多处理器系统的优势
- 线程的同步问题由内核来完成。隶属于不同的进程的线程之间也能共享互斥锁,可以实现跨进程的线程同步。
创建线程和结束线程
pthread_create
创建一个线程的函数是pthead_create。其定义如下:
1 |
|
参数说明:
- thread:新线程的标识符, 实际是一个整型,并且,Linux上几乎所有的资源标识符都是一个整型数,比如socket。
- attr:用于设置新线程的属性,传递NULL表示使用默认线程属性
- start_routing和arg:分别指定新线程将运行的函数及其参数。
返回:
成功时返回0,失败时返回错误码。一个用户可以打开的线程数量不能超过RLIMIT_NPROC软资源限制。此外,系统上所有能创建的线程总数也不能超过/proc/sys/kernel/threads-max 内核参数所定义的值。
pthread_exit
1 |
|
pthread_exit函数通过retval参数向线程的回收者传递其退出信息。它执行完之后不会返回到调用者,而且永远不会失败。
pthread_join
回收其他线程
1 |
|
一个进程中的所有线程都可以调用pthread_join函数来回收其他线程,即等待其他线程结束,这类似于回收进程的wait和waitpid系统调用。
pthread_cancel
有时候我们希望异常终止一个线程,即取消线程。
1 |
|
线程属性
pthread_attr_t结构体定义了一套完整的线程属性。
1 |
|
各种线程属性全部包含在一个字符数组中,并且线程库定义了一系列函数操作pthread_attr_t类型的变量,以方便我们获取和设置线程属性
线程同步
三种方式:
- POSIX信号量
- 互斥锁
- 条件变量