目录
一、线程和进程
1.1 线程的基本概念
1.2 线程的优缺点
二、创建线程
2.1 pthread_create() - 创建线程函数
三、线程属性
3.1 pthread_attr_t类型
3.2 phread_t类型
四、线程退出
4.1 pthread_exit()
4.2 pthread_join()
4.3 pthread_detach()
一、线程和进程
1.1 线程的基本概念
在很多Linux的书籍基本上都是这样子描述进程(process)和线程(thread)的: 进程是资源管理的最小单位,线程是程序执行的最小单位。
在操作系统设计上,从进程演化出线程。 线程的出现最主要的目的就是减小进程上下文切换开销,进程是资源管理的最小单位,那么每个进程都拥有自己的数据段、代码段和堆栈段, 这必然就造成了进程间切换时都要进行比较复杂的上下文切换等动作,因为要保存当前进程上下文的内容, 还要恢复另一个进程的上下文。如果是经常切换进程的话,这样子的开销就过于庞大。因为在进程切换上下文时, 需要重新映射虚拟地址空间、进出OS内核、寄存器切换,还会干扰处理器的缓存机制。 因此为了进一步减少CPU在进程切换时的额外开销,Linux进程演化出了另一个概念——线程。
线程是操作系统能够调度和执行的基本单位,在Linux中也被称之为轻量级进程。在Linux系统中, 一个进程至少需要一个线程作为它的指令执行体,进程管理着资源(比如cpu、内存、文件等等),而将线程分配到某个CPU上执行。在CPU眼中只认PCB(task_struct),不分进程和线程,进程中的每一个task_struct就是一个线程,他们指向同一个地址空间。
task_struct是进程内部的一个执行流,进程可以重新理解为内部只有一个执行流的进程,多线程可以理解为内部具有多个执行流的进程。
1.2 线程的优缺点
线程的优点:
- 创建一个新线程的代价要比创建一个新进程小得多
- 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
- 线程占用的资源要比进程少很多
- 能充分利用多处理器的可并行数量
- 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
- 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
- I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
线程的缺点:
- 性能损失
- 健壮性降低
- 缺乏访问控制
- 编程难度提高
线程共享进程数据,但也拥有自己的一部分数据:
- 线程ID
- 一组寄存器
- 栈
- errno
- 信号屏蔽字
- 调度优先级
进程的多个线程共享同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:
- 文件描述符表
- 每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)
- 当前工作目录
- 用户id和组id
二、创建线程
可移植操作系统接口 (Portable Operating System Interface,缩写为POSIX), POSIX是IEEE为了在各种UNIX操作系统上运行软件而定义API接口等一系列互相关联的标准的总称, 其正式称呼为IEEEStd 1003,而国际标准名称为ISO/IEC9945,此标准源于一个大约开始于1985年的项目。 POSIX这个名称是由理查德·斯托曼(RMS)应IEEE的要求而提议的一个易于记忆的名称。 它基本上是Portable Operating System Interface(可移植操作系统接口)的缩写, 而X则表明其对Unix API的传承。
在Linux系统下的多线程遵循POSIX标准,而其中的一套常用的线程库是 pthread,它是一套通用的线程库,是由 POSIX提出的,因此具有很好的可移植性,Linux多线程编程也正是使用它,在使用时必须包含以下头文件:
#include <pthread.h>
除此之外在链接时需要使用库libpthread.a。因为pthread的库不是Linux系统的库, 所以在编译时要加上-lpthread 选项。
2.1 pthread_create() - 创建线程函数
pthread_create()函数是用于创建一个线程的,创建线程实际上就是确定调用该线程函数的入口点, 在线程创建后,就开始运行相关的线程函数。函数原型如下:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
参数说明:
thread:指向线程标识符的指针。
attr:设置线程属性,attr为NULL表示默认属性。
start_routine:start_routine是一个函数指针,指向要运行的线程入口,即线程运行时要执行的函数代码。
arg:给start_routine指向的函数传入的参数。
返回值:若线程创建成功,则返回0。若线程创建失败,则返回对应的错误代码。
三、线程属性
3.1 pthread_attr_t类型
上面pthread_create中需要以线程属性作为输入参数,在Linux中线程属性结构如下:
typedef struct
{int etachstate; //线程的分离状态int schedpolicy; //线程调度策略structsched_param schedparam; //线程的调度参数int inheritsched; //线程的继承性int scope; //线程的作用域size_t guardsize; //线程栈末尾的警戒缓冲区大小int stackaddr_set; //线程的栈设置void* stackaddr; //线程栈的位置size_t stacksize; //线程栈的大小
}pthread_attr_t;
线程的属性非常多,而且其属性值不能直接设置,须使用相关函数进行操作。线程属性主要包括如下属性: 作用域(scope)、栈大小(stacksize)、栈地址(stackaddress)、优先级(priority)、 分离的状态(detachedstate)、调度策略和参数(scheduling policy and parameters)。 默认的属性为非绑定、非分离、1M的堆栈大小、与父进程同样级别的优先级,无其他特别需求,是可以不需要考虑线程相关属性的,使用默认的属性即可。
3.2 phread_t类型
pthread_ create函数会产生一个线程ID,存放在第一个参数thread中,该参数指向一个虚拟地址空间中共享区的某块区域,该区域用于管理线程的栈结构,且主线程使用的为内核提供的栈结构,子线程使用的是该区域提供的栈结构(一个多线程中包含一个主线程和多个子线程),以此来实现线程之间栈相对独立,该区域的起始地址即为新创建线程的线程ID,线程库NPTL提供了pthread_ self()函数,该函数的返回值为线程自身的ID。
pthread_t pthread_self(void);
四、线程退出
4.1 pthread_exit()
在线程创建后,系统就开始运行相关的线程函数,在该函数运行完之后,该线程也就退出了, 这是线程的一种隐式退出的方法,这与进程的退出差不多,进程完成工作后就会退出。 而另一种退出线程的方法是使用pthread_exit()函数,让线程显式退出,这是线程的主动行为。 这里要注意的是,在使用线程函数时,不能随意使用exit()退出函数来进行出错处理, 这是因为exit()函数的作用是使调用进程终止,而一个进程往往包含多个线程,因此,在使用exit()之后,该进程中的所有线程都会被退出,因此在线程中只能调用线程退出函数pthread_exit()而不是调用进程退出函数exit(),pthread_exit()函数原型如下:
void pthread_exit(void *retval);
参数说明:
retval:如果retval不为空,则会将线程的退出值保存到retval中,如果不关心线程的退出值,形参为NULL即可。
4.2 pthread_join()
一般情况下,进程中各个线程的运行是相互独立的,线程的终止并不会相互通知,也不会影响其他的线程, 终止的线程所占用的资源不会随着线程的终止而归还系统,而是仍为线程所在的进程持有,这是因为一个进程中的多个线程是共享数据段的。进程之间可以使用wait()系统调用来等待其他进程结束,线程也有类似的函数:
int pthread_join(pthread_t thread, void **rval_ptr);
如果某个线程想要等待另一个线程退出,并且获取它的退出值,那么就可以使pthread_join()函数完成,以阻塞的方式等待tid指定的线程结束,当函数返回时,被等待线程的资源将被收回,如果进程已经结束,那么该函数会立即返回。并且tid指定的线程必须是可结合状态的,该函数执行成功返回0,否则返回对应的错误代码。
参数说明:
thread: 线程标识符,即线程ID,标识唯一线程。
rval_ptr: 用户定义的指针,用来存储被等待线程的返回值。
需要注意的是一个可结合状态的线程所占用的内存仅当有线程对其执行立pthread_join()后才会释放,因此为了避免内存泄漏,所有线程的终止时,要么已被设为detached,要么使用pthread_join()来回收资源。
4.3 pthread_detach()
默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。如果不关心线程的返回值,join是一种负担,这个时候,可以通过pthread_detach()函数将线程设置为detached,当线程退出时,自动释放线程资源,pthread_detach()函数如下:
int pthread_detach(pthread_t thread);
参数说明:
thread: 线程标识符,即线程ID,标识唯一线程。
返回值:成功返回0,失败返回-1
joinable和detached是冲突的,一个线程不能既是joinable又是detached,可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离:pthread_detach(pthread_self())。
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>/*要执行的线程*/
void *test_thread(void *arg)
{int num = (int)arg; printf("This is test thread, arg is %d\n", num);sleep(5);/*退出线程*/pthread_exit(NULL);
}int main(void)
{pthread_t tid;void *thread_return;int arg = 520;int res;printf("start create thread\n");/*创建线程,线程执行test_thread函数*/res = pthread_create(&tid, NULL, test_thread, (void*)(arg));if(res != 0){printf("create thread fail\n");exit(res);}printf("create treads success\n");printf("waiting for threads to finish...\n");/*等待线程终止*/res = pthread_join(tid, &thread_return);if(res != 0){printf("thread exit fail\n");exit(res);}printf("thread exit ok\n");return 0;
}