前言
程序运行在内存空间中叫进程,进程中包含有若干线程,线程是系统调度和执行的基本单位。线程才是程序运行的实体,通常程序里的main()函数就相当于主线程,把进程理解成一个容器,里面可以包含有若干线程和若干资源(进程环境变量、打开的文件描述符、信号量、虚拟地址空间、代码、数据等),同一个进程中,所有线程共享进程所有资源,每个线程也有自己的程序计数器、栈空间和寄存器等。
1. 展望多进程、多线程的优劣
进程是资源分配的最小单位,而线程是程序运行的最小单位。
1.1 多进程
由于进程间是互不干扰的,每个进程都有自己的用户虚拟空间,可靠性高,但数据共享方面就会比较复杂,需要用到 IPC通信机制;
进程内存占用大,进程间的切换系统开销大、切换速度较慢;
它的编程与调试相对简单点。
1.2 多线程
同一进程下的多个线程通信容易,因为它们处于同一个虚拟空间下;
其中一个线程调用exit()或其它函数退出,会导致整个进程都退出,不太可靠;
线程占用内存少,线程间切换速度快,创建、销毁线程速度也比进程快;
编程与调试相对复杂。
2. 线程概述
进程有唯一的进程号PID,线程也不例外,也有唯一的ID号,线程ID不同于进程ID,它是只有在进程运行的时候才有意义。
linux系统中,线程ID是一个无符号长整形数,可以通过pthread_self(void)函数获取线程ID。
在shell终端编译链接的时候,由于pthread不在gcc默认的链接库中,需要在编译命令中使用 -l 选项指定链接库pthread,格式为:gcc -o app app.c -lpthread。
2. 线程操作集API
2.1 创建线程
头文件:
#include <pthread.h>
函数原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg)
参数:
thread:一个传出参数,指向线程ID的指针;
attr:设置线程属性;通常设为NULL,表示使用线程默认属性;
start_routine:一个函数指针,指向线程运行的起始地址;
arg:传给线程执行函数的参数,通常设为NULL,表示不需要传参;
返回值:
成功:返回 0 ;
失败:返回错误编号。
2.2 退出线程
在多线程的应用中,通常不会调用exit()等函数退出,这会造成整个进程一并退出,影响到其它还要运行的线程,所以通常是调用pthread_exit函数逐个退出线程。
头文件:
#include <pthread.h>
函数原型:
void pthread_exit(void *retval)
参数:
retval:表示线程的退出状态,通常设置为NULL。
2.3 回收线程
调用pthread_join函数会阻塞等待线程的终止,并获取退出码。如果指定线程在调用pthread_join函数之前就已经退出了,则函数立马返回。pthread_join不能回收已处于death状态的线程。
头文件:
#include <pthread.h>
函数原型:
int pthread_join(pthread_t thread, void **retval)
参数:
thread:指定回收线程ID;
retval:线程退出返回值;不关心退出码则设为NULL;
返回值:
成功:返回 0 ;
失败:返回错误码。
示例程序:
一个进程中有三个线程,主线程创建完线程1和线程2后就退出了,线程1阻塞等待线程2的退出,线程2创建后延迟5s退出。
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
pthread_t pth1,pth2;
void *ret = NULL;
void *pthread_1(void *arg)
{
printf("this is pthread_1.pth-id:%ld\n",pthread_self());
pthread_join(pth2,&ret);
printf("pth2 has exit.ret:%d\n",ret);
pthread_exit(NULL);
}
void *pthread_2(void *arg)
{
printf("this is pthread_2.pth-id:%ld\n",pthread_self());
sleep(5);
pthread_exit((void *)5);
}
int main()
{
if(pthread_create(&pth2,NULL,pthread_2,NULL) != 0){
perror("pthread_2 create");
exit(0);
}
if(pthread_create(&pth1,NULL,pthread_1,NULL) != 0){
perror("pthread_1 create");
exit(0);
}
printf("main pth-id:%ld\n",pthread_self());
pthread_exit(0);
return 0;
}
2.4 取消指定线程
调用pthread_cancel函数后,立马返回,不会等待线程退出后才返回。pthread_exit是主动退出,而pthread_cancel是被动退出的。
头文件:
#include <pthread.h>
函数原型:
int pthread_cancel(pthread_t thread)
参数:
thread:指定线程ID;
返回值:
成功:返回 0 ;
失败:返回错误码。
示例程序:
一个进程中有三个线程,主线程创建完线程1和线程2后,延迟3s调用pthread_cancel函数被动退出线程2,线程1阻塞等待线程2的退出。(如果把线程2中while(1)里的sleep函数去掉,你会发现线程2不响应cancel信号,这是因为没有触发时间点,下面有解析“时间点”)。
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
pthread_t pth1,pth2;
void *ret = NULL;
void *pthread_1(void *arg)
{
printf("this is pthread_1.pth-id:%ld\n",pthread_self());
pthread_join(pth2,&ret);
printf("pth2 has exit.ret:%d\n",ret);
pthread_exit(NULL);
}
void *pthread_2(void *arg)
{
printf("this is pthread_2.pth-id:%ld\n",pthread_self());
while(1)
{
printf("pth-2\n");
sleep(1);
}
}
int main()
{
if(pthread_create(&pth2,NULL,pthread_2,NULL) != 0){
perror("pthread_2 create");
exit(0);
}
if(pthread_create(&pth1,NULL,pthread_1,NULL) != 0){
perror("pthread_1 create");
exit(0);
}
printf("main pth-id:%ld\n",pthread_self());
sleep(3);
pthread_cancel(pth2);
pthread_exit(0);
return 0;
}
2.4.1 线程取消状态
默认情况下,线程会响应pthread_cancel函数的调用,但也可以设置为不响应取消线程。
线程为不响应取消时,如果接收到取消请求,则会将请求挂起,直至线程的取消性状态变为响应。
头文件:
#include <pthread.h>
函数原型:
int pthread_setcancelstate(int state, int *oldstate)
参数:
state:设置新的响应状态;
PTHREAD_CANCEL_ENABLE:线程响应取消;
PTHREAD_CANCEL_DISABLE:线程不响应取消;
oldstate:保存旧的响应状态;NULL,表示不关心旧状态;
返回值:
成功:返回 0;
失败:返回错误码。
2.4.2 线程取消类型
在线程状态设置为PTHREAD_CANCEL_ENABLE的情况下,调用pthread_setcanceltype函数设置线程响应 Cancel 信号的时间点,默认值为PTHREAD_CANCEL_DEFERRED。
说说什么叫时间点,可以理解从某函数的调用,如果没有达到时间点系统会认为还在执行重要指令,就不会响应取消线程,下图罗列一些触发时间点函数(也可以shell终端输入命令“man 7 pthreads”查看详细内容)。
头文件:
#include <pthread.h>
函数原型:
int pthread_setcanceltype(int type, int *oldtype)
参数:
type:设置线程类型;
PTHREAD_CANCEL_DEFERRED:当线程执行到某个可作为取消点的函数时终止执行;
PTHREAD_CANCEL_ASYNCHRONOUS:接收到 Cancel 信号后立即结束执行;
oldtype:保存线程旧的类型;设为NULL表示不关心旧类型;
返回值:
成功:返回 0;
失败:返回错误码。
2.5 线程分离
调用pthread_join()获取其返回状态、回收线程资源,它会阻塞等待,就显得很不友好了;
调用pthread_detach函数将线程分离,当线程结束后,它的退出状态不由其它线程获取,而是由该线程自身自动释放。
头文件:
#include <pthread.h>
函数原型:
int pthread_detach(pthread_t thread)
参数:
thread:指定分离线程ID;
返回值:
成功:返回0;
失败:返回错误码。
示例程序:
线程2中调用分离线程函数将线程2分离出去了,线程1中调用回收线程函数,从打印信息可以看出,线程2还没有退出的时候,pthread_join函数就返回退出了。
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
pthread_t pth1,pth2;
void *ret = NULL;
void *pthread_1(void *arg)
{
printf("this is pthread_1.pth-id:%ld\n",pthread_self());
pthread_join(pth2,&ret);
printf("ret:%d\n",ret);
pthread_exit(NULL);
}
void *pthread_2(void *arg)
{
pthread_detach(pth2);
printf("this is pthread_2.pth-id:%ld\n",pthread_self());
sleep(5);
printf("pth2 will exit.\n");
pthread_exit((void *)5);
}
int main()
{
if(pthread_create(&pth2,NULL,pthread_2,NULL) != 0){
perror("pthread_2 create");
exit(0);
}
sleep(1);
if(pthread_create(&pth1,NULL,pthread_1,NULL) != 0){
perror("pthread_1 create");
exit(0);
}
printf("main pth-id:%ld\n",pthread_self());
pthread_exit(NULL);
return 0;
}
3. 线程的属性
线程创建函数pthread_create()的第三个参数可以设置线程的属性const pthread_attr_t *attr。
pthread_attr_t结构体内容如下:
typedef struct
{
int etachstate; //线程的分离状态
int schedpolicy; //线程调度策略
struct sched_param schedparam; //线程的调度参数
int inheritsched; //线程的继承性
int scope; //线程的作用域
size_t guardsize; //线程栈末尾的警戒缓冲区大小
int stackaddr_set; //线程栈的设置
void* stackaddr; //线程栈的位置
size_t stacksize; //线程栈的大小
} pthread_attr_t;
3.1 初始化与销毁属性
该结构体成员变量的值不能随意更改,需要在线程创建前调用pthread_attr_init函数初始化;
线程退出时调用pthread_attr_destroy函数销毁属性资源。
头文件:
#include <pthread.h>
函数原型:
int pthread_attr_init(pthread_attr_t *attr) //attr设为NULL表示默认配置属性;
int pthread_attr_destroy(pthread_attr_t *attr)返回值:
成功:返回0;
失败:返回错误码。
3.2 分离状态
线程的分离状态决定一个线程终止自身运行的方式,默认情况下线程处于非分离状态。
头文件:
#include <pthread.h>
函数原型:
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate) //设置分离状态;
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate)//获取分离状态;
参数:
detachstate:分离状态属性;
PTHREAD_CREATE_DETACHED(1):以分离状态启动线程,无法被其它线程回收;
PTHREAD_CREATE_JOINABLE(0):默认值,非分离状态启动,可以被其它线程回收;
返回值:
成功:返回0;
失败:返回错误码。
3.3 调度策略
分时调度策略通过nice值和counter值决定调度权值,nice值越小、counter越大,被调用的概率越高。
实时调度策略通过优先级决定调度权值;设置为SCHED_FIFO时,当前优先级最高者获得cpu运行,而同优先级下设置为SCHED_RR时,按照时间片轮询。
头文件:
#include <pthread.h>
函数原型:
int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy) //设置调度策略;
int pthread_attr_getschedpolicy(pthread_attr_t *attr, int *policy) //获取调度策略;参数:
policy:
SCHED_OTHER:分时调度策略;
SCHED_FIFO,实时调度策略,先到先得;
SCHED_RR,实时调度策略,按时间片轮询。
返回值:
成功:返回0;
失败:返回错误码。
3.4 栈属性
线程的栈用于存储线程的私有数据。
头文件:
#include <pthread.h>
函数原型:
int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize)
int pthread_attr_getstack(const pthread_attr_t *attr, void **stackaddr, size_t *stacksize)
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize)
int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize)
int pthread_attr_setstackaddr(pthread_attr_t *attr, void *stackaddr)
int pthread_attr_getstackaddr(const pthread_attr_t *attr, void **stackaddr)
参数:
stackaddr:栈起始地址;
stacksize:栈大小;
返回值:
成功:返回0;
失败:返回错误码。
测试例程:
创建线程前初始化属性,设置分离状态为PTHREAD_CREATE_DETACHED(值为1),然后创建一个线程,并在线程里获取分离状态,打印出来。
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
pthread_attr_t attr;
void *pthread_1(void *arg)
{
int detachstate;
printf("this is pthread_1.pth-id:%ld\n",pthread_self());
pthread_attr_getdetachstate(&attr,&detachstate);
printf("detachstate = %d\n",detachstate);
pthread_exit(NULL);
}
int main()
{
pthread_t pth1;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
if(pthread_create(&pth1,NULL,pthread_1,NULL) != 0){
perror("pthread_1 create");
exit(0);
}
printf("main pth-id:%ld\n",pthread_self());
pthread_exit(NULL);
return 0;
}