Linux多线程之基本编程

线程概念

线程是指运行中的程序的调度单位。一个线程指的是进程中一个单一顺序的控制流,也被称为轻量级线程。它是系统独立调度和分配的基本单位。同一进程中的多个线程将共享该系统中的全部系统资源,比如文件描述符和信号处理等。一个进程可以有很多线程,每个线程并行执行不同的任务。和进程相比,它是一种非常“节俭”的多任务操作方式。在Linux系统中,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护其代码段、堆栈段和数据段,这种多任务工作方式的代价非常“昂贵”。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且线程间彼此切换所需要时间也远远小于进程间切换所需要的时间。

线程间方便的通信机制。对不同进程来说它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行。这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其他线程所用,不仅方便,而且快捷。

进程与线程

线程基本编程

Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。因为pthread的库不是Linux系统的库,所以在编译时要加上 -lpthread。例如:gcc filename -lpthread。注意,这里要讲的线程相关操作都是用户空间中的线程的操作。

线程创建:

创建线程实际上就是确定调用该线程函数的入口点,这里通常使用的函数是pthread_create()。在线程创建后,就开始运行相关的线程函数。

pthread_create()函数
所需头文件 #include <pthread.h>
函数原型 int pthread_create ((pthread_t *thread, pthread_attr_t *attr,void *(*start_routine)(void *), void *arg))
函数传入值
        thread:该参数是指向线程标识符的指针,当线程创建成功时,用来返回创建的线程ID
        attr:线程属性设置,通常取为 NULL表示使用默认属性
        start_routine:该函数为一个函数指针,指向线程创建后要调用的函数,是一个以指向 void 的指针
        作为参数和返回值的函数指针。
        arg:指向传递给线程函数的参数
函数返回值 成功:0 出错:返回错误码

线程退出

在线程创建后,就开始运行相关的线程函数,在该函数运行完之后,该线程也就退出了,这也是线程退出的一种方法。另一种退出线程的方法是使用函数pthread_exit(),这是线程的主动行为。这里要注意的是,在使用线程函数时,不能随意使用exit()退出函数来进行出错处理。由于exit()的作用是使调用进程终止,而一个进程往往包含多个线程,因此,在使用exit()之后,该进程中的所有线程都终止了。在线程中就可以使用pthread_exit()来代替进程中的exit()。

pthread_exit()函数
所需头文件 #include <pthread.h>
函数原型   void pthread_exit(void *retval)
函数传入值 retval:线程结束时的返回值,可由pthread_join()函数来获取

线程等待

由于一个进程中的多个线程是共享数据段的,因此,通常在线程退出后,退出线程所占用的资源并不会随着线程的终止而得到释放。正如进程之间可以用wait()系统调用来同步终止并释放资源一样,线程之间也有类似机制,那就是pthread_join()函数。pthread_join()用于将当前进程挂起来等待线程的结束。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源就被收回。

pthread_join()函数
所需头文件 #include <pthread.h>
函数原型  int pthread_join ((pthread_t thread, void **thread_return))
函数传入值 
        thread:等待退出的线程ID
        thread_return:用户定义的指针,用来存储被等待线程结束时的返回值(不为 NULL时)
函数返回值 成功:0 出错:返回错误码

线程取消

前面已经提到线程调用pthread_exit()函数主动终止自身线程,但是在很多线程应用中,经常会遇到在别的线程中要终止另一个线程的问题,此时调用pthread_cancel()函数来实现这种功能,但在被取消的线程的内部需要调用pthread_setcancel()函数和pthread_setcanceltype()函数设置自己的取消状态。例如,被取消的线程接收到另一个线程的取消请求之后,是接受还是忽略这个请求;如果是接受,则再判断立刻采取终止操作还是等待某个函数的调用等。

pthread_cancel()函数
所需头文件 #include <pthread.h>
函数原型  int pthread_cancel(pthread_t thread)
函数传入值 thread:要取消的线程的标识符ID
函数返回值 成功:0 出错:返回错误码

线程标识符获取

获取调用线程的标识ID。

pthread_self()函数
所需头文件 #include <pthread.h>
函数原型  pthread_t pthread_self(void)
函数返回值 返回调用该函数的线程的标识ID

线程清除

线程终止有两种情况:正常终止和非正常终止。线程主动调用pthread_exit()或者从线程函数中return都将使线程正常退出,这是可预见的退出方式;非正常终止是线程在其它线程的干预下,或者由于自身运行出错(比如访问非法地址)而退出,这种退出方式是不可预见的。不论是可预见的线程终止还是异常终止,都回存在资源释放的问题,如何保证线程终止时能顺利地释放掉自己所占用的资源,是一个必须考虑的问题。从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作(包括调用pthread_exit()和异常终止,不包括return)都将执行pthread_cleanup_push()所指定的清理函数。

pthread_cleanup_push()函数
所需头文件 #include <pthread.h>
函数原型  void pthread_cleanup_push(void (*routine)(void *),void *arg);
函数传入值 routine:清除函数
         arg:    清除函数的参数
函数功能说明:将清除函数压入清理函数栈中

pthread_cleanup_pop()函数
所需头文件 #include <pthread.h>
函数原型 void pthread_cleanup_pop(int execute);
函数传入值 execute:执行到pthread_cleanup_pop()时是否在弹出清理函数的同时执行该函数。非0:执行 0:不执行 
                  这个参数并不影响异常终止时清理函数的执行。
函数功能说明:将清除函数弹出清理函数栈

pthread_cleanup_push()/pthread_cleanup_pop()采用先入后出的栈结构管理,void routine(void *arg)函数在调用pthread_cleanup_push()时压入清理函数栈,多次对pthread_cleanup_push()的调用将在清理函数栈中形成一个函数链,在执行该函数链时按照压栈的相反顺序弹出。execute参数表示执行到pthread_cleanup_pop()时是否在弹出清理函数的同时执行该函数,为0表示不执行,非0为执行;这个参数并不影响异常终止时清理函数的执行。

pthread_cleanup_push()/pthread_cleanup_pop()是以宏方式实现的,这是pthread.h中的宏定义:

#define pthread_cleanup_push(routine,arg)                                     
  { struct _pthread_cleanup_buffer _buffer;                                   
    _pthread_cleanup_push (&_buffer, (routine), (arg));
#define pthread_cleanup_pop(execute)                                          
    _pthread_cleanup_pop (&_buffer, (execute)); }

pthread_cleanup_push()带有一个”{“,而pthread_cleanup_pop()带有一个”}”,因此这两个函数必须成对出现,且必须位于程序的同一级别的代码段中才能通过编译。
注意:在线程宿主函数中主动调用return,如果return语句包含在pthread_cleanup_push()/pthread_cleanup_pop()对中,则不会引起清理函数的执行

在下面三种情况下,pthread_cleanup_push()压栈的“清理函数”会被调用:
a. 线程调用pthread_exit()函数,而不是直接return.
b. 响应取消请求时,也就是有其它的线程对该线程调用pthread_cancel()函数。
c. 本线程调用pthread_cleanup_pop()函数,并且其参数非0

Linux多线程Demo

下面的一个linux多线程的测试程序 将会帮我们更好的理解和掌握多线程的操作

/*pthread_cleanup_1.c*/
#include <stdio.h>  
#include <stdlib.h>  
#include <pthread.h>  

#define PTHREAD_TEST 1

void cleanup(void *arg)  
{  
    printf("cleanup:%s\n",(char*)arg);  
}  

void *thr_fn1(void *arg)
{
    printf("thread 1 start\n");
    /*连续压入两次清理函数到清理函数栈中 注意参数不同*/
    pthread_cleanup_push(cleanup,"thread 1 first handler");
    pthread_cleanup_push(cleanup,"thread 1 second handler");
    printf("thread 1 push complete\n");

    if(arg)
    {
        return ((void *)1);/*return 不会引起清理函数的执行*/
    }

    /*若执行到此处则表明线程没有异常退出*/
    pthread_cleanup_pop(0);/*0表示仅从清理函数栈中弹出该清理函数,但并不执行*/
    pthread_cleanup_pop(0);
    return ((void *)1);
}

void *thr_fn2(void *arg)
{
    printf("thread 2 start\n");
    /*连续压入两次清理函数到清理函数栈中 注意参数不同*/
    pthread_cleanup_push(cleanup,"thread 2 first handler");
    pthread_cleanup_push(cleanup,"thread 2 second handler");
    printf("thread 2 push complete\n"); 

    if(arg)
    {
        pthread_exit((void *)2);
    }

    /*若执行到此处则表明线程没有异常退出*/
    pthread_cleanup_pop(0);/*0表示仅从清理函数栈中弹出该清理函数,但并不执行*/
    pthread_cleanup_pop(0);

    pthread_exit((void *)2);
}

void clean_fun1(void * arg)  
{  
    printf("this is clean fun1\n");  
}  
void clean_fun2(void * arg)  
{  
    printf("this is clean fun2\n");  
} 

void * thread_fun(void * arg)  
{  
    printf("thread 3 start\n");
    pthread_cleanup_push(clean_fun1,NULL);  
    pthread_cleanup_push(clean_fun2,NULL);  
    printf("thread 3 push complete\n"); 

    sleep(30);  

    pthread_cleanup_pop(0);  
    pthread_cleanup_pop(0);  
    return NULL;  
} 
int main()
{
    int err;
    pthread_t tid1,tid2,tid3;
    void *tret;

    err=pthread_create(&tid1,NULL,thr_fn1,(void *)1);
    if(err!=0)
    {
        printf("thread create 1 is error\n");
        return -1;
    }
    err=pthread_create(&tid2,NULL,thr_fn2,(void *)1);
    if(err != 0)  
    {  
        printf("thread create 2 is error\n");  
        return -1;  
    } 

    err=pthread_join(tid1,&tret);/*等待线程1退出*/
    if(err != 0)  
    {  
        printf("can't join with thread 1\n");  
        return -1;  
    }  
    printf("thread 1 exit code %d\n",(int)tret);  


    err = pthread_join(tid2,&tret);/*等待线程2退出*/  
    if(err != 0)  
    {  
        printf("can't join with thread 2\n");  
        return -2;  
    }  
    printf("thread 2 exit code %d\n",(int)tret);  


    /*测试外部关闭线程*/
    err=pthread_create(&tid3,NULL,thread_fun,NULL);  
    if(err!=0)  
    {  
        perror("pthread_create");  
        exit(0);  
    }  

    sleep(3);  

    #if PTHREAD_TEST
        err=pthread_cancel(tid3);  
        if(err!=0)  
        {  
            perror("cancel error:");  
            exit(0);  
        }  
        err=pthread_join(tid3,NULL);  
        if(err != 0)  
        {  
            printf("can't join with thread 3\n");  
            return -3;  
        }
        printf("thread 3 exit");
    #else
        printf("app exit\n");  
        exit(1);
    #endif

    return 0;  
}

先修改程序中PTHREAD_TEST 为 1
编译:gcc pthread_cleanup_1.c -o pthread_cleanup_1 -lpthread
编译时会提示以下类型转换警告 忽略即可

pthread_cleanup_1.c:100:38: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
     printf("thread 1 exit code %d\n",(int)tret);  
                                      ^
pthread_cleanup_1.c:109:38: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
     printf("thread 2 exit code %d\n",(int)tret); 

执行:./pthread_cleanup_1

运行输出

$ ./pthread_cleanup_1
thread 1 start
thread 1 push complete
thread 2 start
thread 2 push complete
thread 1 exit code 1
cleanup:thread 2 second handler
cleanup:thread 2 first handler
thread 2 exit code 2
thread 3 start
thread 3 push complete
this is clean fun2
this is clean fun1

修改程序中PTHREAD_TEST 为 0 ,然后在重新编译、执行
运行输出

$ ./pthread_cleanup_1
thread 2 start
thread 2 push complete
thread 1 start
thread 1 push complete
thread 1 exit code 1
cleanup:thread 2 second handler
cleanup:thread 2 first handler
thread 2 exit code 2
thread 3 start
thread 3 push complete
app exit

从输出结果可以看出:thr_fn1,thr_fn2两个线程都调用了,但是却只调用了第二个线程的清理处理程序,
所以如果线程是通过从它的运行过程中return返回而终止的话,那么它的清理处理程序就不会被调用,
还要注意清理程序是按照与它们安装时相反的顺序被调用的。
从代码输出也可以看到先执行的thread 2 second handler后执行的thread 2 first handler。

通过改变PTHREAD_TEST宏可以测试两种情况
1 调用pthread_cancel关闭指定进程时,会引起清理函数弹出并执行
0 进程突然退出时,不会引起清理函数的执行

多运行几次,例如上面的两次运行输出,你会发现线程运行输出的打印信息的顺序并不完全都是相同的 这也表明线程执行的顺序是杂乱无章的。

猜你喜欢

转载自blog.csdn.net/makercloud/article/details/80230541