一、posix 线程概述
进程在各自独立的地址空间中运行,进程之间共享数据需要用进程间通信机制,有些情况需要在一个进程中同时执行多个控制流程,这时候线程就派上了用场,比如实现一个图形界面的下载软件,一方面需要和用户交互,等待和处理用户的鼠标键盘事件,另一方面又需要同时下载多个文件,等待和处理从多个网络主机发来的数据,这些任务都需要一个“等待-处理”的循环,可以用多线程实现,一个线程专门负责与用户交互,另外几个线程每个线程负责和一个网络主机通信。
main函数和信号处理函数是同一个进程地址空间中的多个控制流程,多线程也是如此,但是比信号处理函数更加灵活,信号处理函数的控制流程只是在信号递达时产生,在处理完信号之后就结束,而多线程的控制流程可以长期并存,操作系统会在各线程之间调度和切换,就像在多个进程之间调度和切换一样。由于同一进程的多个线程共享同一地址空间,因此TextSegment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:
文件描述符表
每种信号的处理方式(SIG_IGN、SIG_DFL或者自定义的信号处理函数)
当前工作目录用户id和组id
但有些资源是每个线程各有一份的:
线程id
上下文,包括各种寄存器的值、程序计数器和栈指针
栈空间
errno变量
信号屏蔽字
调度优先级
进程有代码段、数据段和栈段,而线程与进程中的 其他线程共享代码段和数据段,每个线程都有自己的栈段,这个栈段在进程地址空间的栈 段中进行分配。线程栈的尺寸在线程创建时设置。如果在创建时没有设置,那么系统将会指定一个默认值,缺省值的大小依赖于具体的系统。
我们将要学习的线程库函数是由POSIX标准定义的,称为POSIX thread或者pthread。在Linux上线程函数位于libpthread共享库中,因此在编译时要加上-lpthread选项。
二、pthread 系列函数
(一)
功能:创建一个新的线程
原型 intpthread_create(pthread_t *thread, const pthread_attr_t *attr, void*(*start_routine)(void*), void *arg);
参数
thread:返回线程ID
attr:设置线程的属性,attr为NULL表示使用默认属性
start_routine:是个函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码
错误检查:
以前学过的系统函数都是成功返回0,失败返回-1,而错误号保存在全局变量errno中,而pthread库的函数都是通过返回值返回错误号,虽然每个线程也都有一个errno,但这是为了兼容其它函数接口而提供的,pthread库本身并不使用它,通过返回值返回错误码更加清晰。由于pthread_create的错误码不保存在errno中,因此不能直接用perror(3)打印错误信息,可以先用strerror(3)把错误码转换成错误信息再打印。
(二)
功能:线程终止
原型 void pthread_exit(void*value_ptr);
参数
value_ptr:value_ptr不要指向一个局部变量,因为当其它线程得到这个返回指针时线程函数已经退出了。
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)
如果需要只终止某个线程而不终止整个进程,可以有三种方法:
1、从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit,而如果任意一个线程调用了exit或_exit,则整个进程的所有线程都终止。
2、一个线程可以调用pthread_cancel 终止同一进程中的另一个线程。
3、线程可以调用pthread_exit终止自己。
(三)
功能:等待线程结束
原型 intpthread_join(pthread_t thread, void **value_ptr);
参数
thread:线程ID
value_ptr:它指向一个指针,后者指向线程的返回值
返回值:成功返回0;失败返回错误码
当pthread_create 中的 start_routine返回时,这个线程就退出了,其它线程可以调用pthread_join得到start_routine的返回值,类似于父进程调用wait(2)得到子进程的退出状态。
调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:
1、如果thread线程通过return返回,value_ptr所指向的单元里存放的是thread线程函数的返回值。
2、如果thread线程被别的线程调用pthread_cancel异常终止掉,value_ptr所指向的单元里存放的是常数PTHREAD_CANCELED。
3、如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。
如果对thread线程的终止状态不感兴趣,可以传NULL给value_ptr参数。
(四)
功能:返回线程ID
原型 pthread_tpthread_self(void);
返回值:成功返回0
在Linux上,pthread_t类型是一个地址值,属于同一进程的多个线程调用getpid(2)可以得到相同的进程号,而调用pthread_self(3)得到的线程号各不相同。线程id只在当前进程中保证是唯一的,在不同的系统中pthread_t这个类型有不同的实现,它可能是一个整数值,也可能是一个结构体,也可能是一个地址,所以不能简单地当成整数用printf打印。
(五)
功能:取消一个执行中的线程
原型 intpthread_cancel(pthread_t thread);
参数
thread:线程ID
返回值:成功返回0;失败返回错误码
一个新创建的线程默认取消状态(cancelability state)是可取消的,取消类型( cancelability type)是同步的,即在某个可取消点( cancellation point,即在执行某些函数的时候)才会取消线程。具体可以man 一下。
相关函数 intpthread_setcancelstate(int state, int *oldstate); intpthread_setcanceltype(int type, int *oldtype);
(六)
功能:将一个线程分离
原型 intpthread_detach(pthread_t thread);
参数
thread:线程ID
返回值:成功返回0;失败返回错误码
一般情况下,线程终止后,其终止状态一直保留到其它线程调用pthread_join获取它的状态为止(僵线程)。但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL。对一个尚未detach的线程调用pthread_join或pthread_detach都可以把该线程置为detach状态,也就是说,不能对同一线程调用两次pthread_join,或者如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了。
posix 线程属性
POSIX 线程库定义了线程属性对象 pthread_attr_t ,它封装了线程的创建者可以访问和修改的线程属性。主要包括如下属性:
1. 作用域(scope)
2. 栈尺寸(stack size)
3. 栈地址(stack address)
4. 优先级(priority)
5. 分离的状态(detached state)
6. 调度策略和参数(scheduling policy and parameters)
线程属性对象可以与一个线程或多个线程相关联。当使用线程属性对象时,它是对线程和线程组行为的配置。使用属性对象的所有线程都将具有由属性对象所定义的所有属 性。虽然它们共享属性对象,但它们维护各自独立的线程 ID 和寄存器。
线程可以在两种竞争域内竞争资源:
1. 进程域(process scope):与同一进程内的其他线程
2. 系统域(system scope):与系统中的所有线程
作用域属性描述特定线程将与哪些线程竞争资源。一个具有系统域的线程将与整个系 统中所有具有系统域的线程按照优先级竞争处理器资源,进行调度。
分离线程是指不需要和进程中其他线程同步的线程。也就是说,没有线程会等待分离 线程退出系统。因此,一旦该线程退出,它的资源(如线程 ID)可以立即被重用。
线程的布局嵌入在进程的布局中。进程有代码段、数据段和栈段,而线程与进程中的 其他线程共享代码段和数据段,每个线程都有自己的栈段,这个栈段在进程地址空间的栈 段中进行分配。线程栈的尺寸在线程创建时设置。如果在创建时没有设置,那么系统将会 指定一个默认值,缺省值的大小依赖于具体的系统。
POSIX 线程属性对象中可设置的线程属性及其含义参见下表:
函数 | 属性 | 含义 |
int pthread_attr_setdetachstate (pthread_attr_t* attr ,int detachstate) | detachstate | detachstate 属性控制一个线程是否 是可分离的 |
int pthread_attr_setguardsize (pthread_attr_t* attr ,size_t guardsize) | guardsize | guardsize 属性设置新创建线程栈的溢出 保护区大小 |
int pthread_attr_setinheritsched (pthread_attr_t* attr, int inheritsched) | inheritsched | inheritsched 决定怎样设置新创建 线程的调度属性 |
int pthread_attr_setschedparam (pthread_attr_t* attr , const struct sched_param* restrict param) | param |
param 用来设置新创建线程的优先级 |
int pthread_attr_setschedpolicy (pthread_attr_t* attr, int policy) | policy | Policy 用来设置先创建线程的调度 策略 |
int pthread_attr_setscope (pthread_attr_t* attr , int contentionscope) | contentionscope | contentionscope 用于设置新创建线 程的作用域 |
int pthread_attr_setstack (pthread_attr_t* attr, void* stackader, size_t stacksize) | stackader stacksize | 两者共同决定了线程栈的基地址 以及堆栈的最小尺寸(以字节为 单位) |
int pthread_attr_setstackaddr(pthread _attr_t* attr, void*stackader) | stackader | stackader 决定了新创建线程的栈的基地址 |
int pthread_attr_setstacksize(pthread_attr_t* attr, size_t stacksize)
stacksize决定了新创建线程的栈的最小尺寸