多线程的开发和应用在平时的项目中使用非常频繁。Linux C++中,一般使用pthread库操作线程相关业务,不少公司一般都会基于该线程库的基本接口进行封装,使用起来更加方便易用。本文对该线程库的常用接口和使用方法做个介绍。
一、创建线程前的工作——设置线程属性
1、线程属性结构体的成分
线程具有属性,属性结构体用pthread_attr_t表示。它的结构如下:
typedef struct
{
int detachstate; //线程的分离状态
int schedpolicy; //线程的调度策略
structsched_param schedparam; //线程的调度参数
int inheritsched; //线程的继承性
int scope; //线程的作用域
size_t guardsize; //线程栈末尾的警戒缓冲区大小
int stackaddr_set; //线程的栈设置
void* stackaddr; //线程栈的位置
size_t stacksize; //线程栈的大小
}pthread_attr_t;
2、属性初始化接口
提供的接口:
int pthread_attr_init(pthread_attr* attr);
使用时,首先要先对这个结构体进行初始化,在使用后需要对其反初始化。
3、属性反初始化接口
提供的接口:
int pthread_attr_destroy(pthread_attr_t* attr);
同上一点的初始化,使用结束后的销毁。
4、设置、获取线程作用域属性scope接口
提供的接口:
int pthread_attr_setscope(pthread_attr_t*, int);
int pthread_attr_getscope(const pthread_attr_t*, int*);
作用域属性表示线程间竞争CPU的范围,也就是说线程优先级的有效范围。具体可以选择两个值,PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS。前者表示与系统中所有线程一起竞争CPU时间,后者表示仅与同进程中的线程竞争CPU,默认的一般都是PTHREAD_SCOPE_PROCESS。
5、设置、获取线程分离状态属性detachstate接口
提供的接口:
int pthread_attr_setdetachstate(pthread_attr_t*, int);
int pthread_attr_getdetachstate(const pthread_attr_t*, int*);
在任何一个时间点上,线程是可结合的(joinable),或者是可分离的(detached),一个可结合的线程能够被其他线程收回其资源和杀死,在被其他线程回收之前,它的存储器资源(如栈)是不释放的。相反,一个分离的线程是不能被其他线程回收或杀死的,它的存储器资源在它终止时由系统自动释放。默认情况下,线程是非分离状态的。这种情况下,原有的线程等待创建的线程结束,只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。这两种状态的值分别是PTHREAD_CREATE_DETACHED(可分离线程)和PTHREAD_CREATE_JOINABLE(非分离线程),默认是PTHREAD_CREATE_JOINABLE。
6、设置、获取线程调度参数接口
提供的接口:
int pthread_attr_setschedparam(pthread_attr_t* restrict, const struct sched_param* param);
int pthread_attr_getschedparam(pthread_attr_t* restrict, const struct sched_param* param);
接口中的struct sched_param结构体仅仅包含一个成员变量sched_priority,大的优先权值对应高的优先权,系统支持的最大和最小优先权值可以用sched_get_priority_max和sched_get_priority_min分别得到。但是在一般情况下,若不是写实时程序,不建议修改线程的优先级,因为调度策略是一个复杂的事情,如果不正确使用会导致程序错误,从而导致死锁问题,普通线程一般不用设置。
7、设置、获取线程的调度策略接口
提供的接口:
int pthread_attr_setschedpolicy(pthread_attr_t*, int);
int pthread_attr_getschedpolicy(const pthread_attr_t*, int*);
Linux线程调度策略及优先级说明:
Linux系统下任务调度策略一般有三种:
SCHED_OTHER:普通任务调度策略。
SCHED_FIFO:实时任务调度策略,先到先服务。一旦占用CPU则一直运行,知道有更高优先级任务到达或自己放弃。
SCHED_RR:实时任务调度策略,时间片轮转。当任务的时间片用完,系统将重新分配时间片,并置于就绪队列尾。普通线程一般设置SCHED_OTHER。
8、设置、获取线程的继承调度
提供的接口:
int pthread_attr_setinheritsched(pthread_attr_t*. int);
int pthread_attr_getinheritsched(const pthread_attr_t*. int*);
线程的继承调度主要是设置线程是否继承父线程的调度,也就是说此线程的调度策略及优先级是否继承于父线程。可以选择两个值:PTHREAD_INHERIT_SCHED和PTHREAD_EXPLICIT_SCHED,默认情况下,普通线程是继承于父线程的。设置好属性之后,接下来就是线程的创建了。
二、创建线程
创建线程接口:
int pthread_create(pthread_t *thread, const pthread_attr_t* attr, void* (*start_routine)(void *), void* arg);
参数说明:
参数一为指向线程标识符的指针,也就是线程句柄。
参数二为设置的线程属性,就是上面第一节中通过接口设置的属性结构体。
参数三为线程运行函数的地址,也就是线程执行体的函数指针。
参数四为线程运行函数的参数。
使用该接口创建线程时,若不指定分配堆栈大小,系统会分配默认值。在Linux中,使用ulimit -s可以查看默认值的大小。Linux默认分配8192KB也就是8M内存。一般来说,默认堆栈是8192KB,最小为16KB。如果在创建线程时,出现内存不足问题,则pthread_create会返回错误码12。
线程创建成功后,设置pthread_detach可以将线程状态更改为detach(分离)状态,正如上面所述,Linux中的线程有两种状态,joinable和detached,分别对应线程结束或退出时,线程创建的栈是否被析构,若为joinable,则不会自动析构,需要手动调用pthread_join()接口进行析构,若是detached,则可以确保资源的释放。线程创建后,调用这个借口将线程设置为detached状态,可以确保资源最终被释放。
int pthread_detach(pthread_t thread);
三、线程阻塞接口
接口定义:
int pthread_join(pthread_t thread, void** retval);
这个接口是将子线程合入主线程,主线程阻塞等待子线程结束,然后回收子线程资源。
四、获取线程ID方法
线程ID的获取可以用gettid方法:
pid_t tid;
tid = syscall(SYS_gettid);
或者使用pthread_self()接口。但gettid和pthread_self()是有区别的。gettid获取的是内核中的线程ID,pthread_self获取的是POSIX thread ID。但gettid不可移植,只能用在LInux系统下,pthread_self()在Linux下与系统thread是一对一模型,可以认为一个gettid必然对应一个pthread_id,但二者之间不可相互转换。
五、线程休眠
线程休眠可以使用sleep()(或者usleep()等)或sched_yield(),但是这两个接口也有一些区别。sced_yield()可以使用另一个级别等于或高于当前线程的线程先运行,如果没有符合条件的线程,那么这个函数将会立刻返回然后继续执行当前线程的程序。而sleep则是等待一段时间后再等待CPU的调度,然后去获得CPU资源,在毫秒级别的usleep下,这种情况可能会导致休眠时间不准。
六、线程正常终止的方法
方法一:return从线程函数返回。
方法二:通过调用函数pthread_exit使线程退出。
方法三:线程可以被统一进程中的其他线程取消。
主线程中,main函数中调用了return或是调用了exit函数,则主线程退出,且整个线程也会终止,此时进程中所有的线程也将终止。
主线程中调用pthread_exit,则仅仅是主线程结束,进程不会结束。进程内的其它线程也不会结束,直到所有的线程结束,进程才会终止。
在任何一个线程中调用exit函数都会导致进程结束。进程一旦结束,所有进程中的线程都将结束。
#include
int pthread_kill(pthread_t thread, int sig);
这个接口作用是向指定ID的线程发送sig信号,如果线程的代码内不作任何信号处理,则会按照信号的默认行为影响整个进程。假如发送了pthread_kill(threadid, SIGKILL),则杀死整个进程。
以上对Linux下常用的pthread线程库做了基本的介绍,也对一些线程机制做了简单的总结。实际使用时,我们可以根据需要调用各自功能的接口实现对多线程的控制。使用该库编译时要加-lpthread。