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
2
3
#include <pthread.h>
int pthread_create ( pthread_t* thread, const pthread_attr_t* attr,
void * (*start_routine)(void*) , void* arg);

参数说明:

  • thread:新线程的标识符, 实际是一个整型,并且,Linux上几乎所有的资源标识符都是一个整型数,比如socket。
  • attr:用于设置新线程的属性,传递NULL表示使用默认线程属性
  • start_routing和arg:分别指定新线程将运行的函数及其参数。

返回:

​ 成功时返回0,失败时返回错误码。一个用户可以打开的线程数量不能超过RLIMIT_NPROC软资源限制。此外,系统上所有能创建的线程总数也不能超过/proc/sys/kernel/threads-max 内核参数所定义的值。

pthread_exit

1
2
#include <pthread.h>
void pthread_exit ( void* retval );

pthread_exit函数通过retval参数向线程的回收者传递其退出信息。它执行完之后不会返回到调用者,而且永远不会失败。

pthread_join

回收其他线程

1
2
#include <pthread.h>
int pthread_join( pthread_t thread, void** retval );

一个进程中的所有线程都可以调用pthread_join函数来回收其他线程,即等待其他线程结束,这类似于回收进程的wait和waitpid系统调用。

pthread_cancel

有时候我们希望异常终止一个线程,即取消线程。

1
2
#include <pthread.h>
int pthread_cancel ( pthread_t thread );

线程属性

pthread_attr_t结构体定义了一套完整的线程属性。

1
2
3
4
5
6
7
#include <bits/pthreadtypes.h>
#define __SIZEOF_PTHREAD_ATTR_T 36
typedef union
{
char __size[__SIZEOF_PTHREAD_ATTR_T];
long int __align;
} pthread_attr_t;

各种线程属性全部包含在一个字符数组中,并且线程库定义了一系列函数操作pthread_attr_t类型的变量,以方便我们获取和设置线程属性

线程同步

三种方式:

  • POSIX信号量
  • 互斥锁
  • 条件变量

互斥锁