linux 线程详解

前言

        程序运行在内存空间中叫进程,进程中包含有若干线程,线程是系统调度和执行的基本单位。线程才是程序运行的实体,通常程序里的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

函数原型:

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

函数原型:

void pthread_exit(void *retval)

参数:

retval:表示线程的退出状态,通常设置为NULL

2.3 回收线程

调用pthread_join函数会阻塞等待线程的终止,并获取退出码。如果指定线程在调用pthread_join函数之前就已经退出了,则函数立马返回。pthread_join不能回收已处于death状态的线程。

头文件:

#include

函数原型:

int pthread_join(pthread_t thread, void **retval)

参数:

thread:指定回收线程ID;

retval:线程退出返回值;不关心退出码则设为NULL;

返回值:

成功:返回 0 ;

失败:返回错误码

示例程序

一个进程中有三个线程,主线程创建完线程1和线程2后就退出了,线程1阻塞等待线程2的退出,线程2创建后延迟5s退出。

#include 
#include 
#include 
#include 

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;
}

linux 线程详解_第1张图片

2.4 取消指定线程

调用pthread_cancel函数后,立马返回,不会等待线程退出后才返回。pthread_exit是主动退出,而pthread_cancel是被动退出的。

头文件:

#include

函数原型:

int pthread_cancel(pthread_t thread)

参数:

thread:指定线程ID;

返回值:

成功:返回 0 ;

失败:返回错误码

示例程序:

一个进程中有三个线程,主线程创建完线程1和线程2后,延迟3s调用pthread_cancel函数被动退出线程2,线程1阻塞等待线程2的退出。(如果把线程2中while(1)里的sleep函数去掉,你会发现线程2不响应cancel信号,这是因为没有触发时间点,下面有解析“时间点”)。

#include 
#include 
#include 
#include 

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;
}

linux 线程详解_第2张图片

2.4.1  线程取消状态

默认情况下,线程会响应pthread_cancel函数的调用,但也可以设置为不响应取消线程。

线程为不响应取消时,如果接收到取消请求,则会将请求挂起,直至线程的取消性状态变为响应。

头文件:

#include

函数原型:

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

函数原型:

int pthread_setcanceltype(int type, int *oldtype)

参数:

type:设置线程类型;

 PTHREAD_CANCEL_DEFERRED:当线程执行到某个可作为取消点的函数时终止执行;

 PTHREAD_CANCEL_ASYNCHRONOUS:接收到 Cancel 信号后立即结束执行;

oldtype:保存线程旧的类型;设为NULL表示不关心旧类型;

返回值:

成功:返回 0;

失败:返回错误码

linux 线程详解_第3张图片

2.5 线程分离

调用pthread_join()获取其返回状态、回收线程资源,它会阻塞等待,就显得很不友好了;

调用pthread_detach函数将线程分离,当线程结束后,它的退出状态不由其它线程获取,而是由该线程自身自动释放。

头文件:

#include

函数原型:

int pthread_detach(pthread_t thread)

参数:

thread:指定分离线程ID;

返回值:

成功:返回0;

失败:返回错误码

示例程序

线程2中调用分离线程函数将线程2分离出去了,线程1中调用回收线程函数,从打印信息可以看出,线程2还没有退出的时候,pthread_join函数就返回退出了。

#include 
#include 
#include 
#include 

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;
}

linux 线程详解_第4张图片

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

函数原型:

int pthread_attr_init(pthread_attr_t *attr)        //attr设为NULL表示默认配置属性;
int pthread_attr_destroy(pthread_attr_t *attr)

返回值:

成功:返回0;

失败:返回错误码

3.2 分离状态

线程的分离状态决定一个线程终止自身运行的方式,默认情况下线程处于非分离状态。

头文件:

#include

函数原型:

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

函数原型:

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

函数原型:

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 
#include 
#include 
#include 

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;
}

你可能感兴趣的:(Linux应用开发,通信,Ubuntu,开发语言,linux,c语言)