• 线程的概念+线程函数API


    C线程

    有关线程的简单实现
    #include
    #include
    #include
    //定义线程函数(固定)--void *
    void *pth_fun(void *pth_arg){
        while(1){
            printf("pthread\n");
            sleep(1);
        }
        return NULL;
    }
    int main(){
        pth_fun(NULL);//先将线程函数作为普通函数调用
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    guojiawei@ubantu-gjw:~/Desktop/pthread$ gcc pthread.c 
    guojiawei@ubantu-gjw:~/Desktop/pthread$ ./a.out 
    pthread
    pthread
    pthread
    pthread
    pthread
    ^Xpthread
    pthread
    ^Z
    [1]+  已停止               ./a.out
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    #include
    #include
    #include
    #include 
    //定义线程函数(固定)--void *
    void *pth_fun(void *pth_arg){
        while(1){
            printf("child_pthread\n");
            sleep(1);
        }
        return NULL;
    }
    int main(){
        /*将函数注册为线程函数*/
        //int pthread_create(pthread_t *thread, const pthread_attr_t *attr,\
                              void *(*start_routine) (void *), void *arg);
        pthread_t tId=0;
        //函数名就是地址
        pthread_create(&tId,NULL,pth_fun,NULL);//主线程
        while(1){
            printf("father\n");
            sleep(2);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    程序结果:

    可以自己链接线程库-pthread,但是现在可以不加

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5Kq6cFYd-1669961351681)(/home/guojiawei/.config/Typora/typora-user-images/image-20221129145844582.png)]

    主线程和次线程是并发运行的,主线程不运行,次线程无法运行

    • 其实主线程是一个进程,目的为了带动线程运行
    • 线程就是一个函数,有的语言会封装

    学习C线程意义

    • c程序经常涉及到多线任务的问题,所以c线程在实际开发中会被经常用到

      使用多线程的话,必须要OS支持

    • 所有的线程库的原理和使用方式都是类似

      理解了C线程函数,在学习其它语言的线程库函数就会容易很多

    为什么会有线程

    为了弥补进程的缺点

    进程最大的优点:拥有独立的进程空间

    可以很好地保证每一个进程的安全,不被其它进程所攻击或者干扰,

    进程的缺点:(也是优点带来的)

    • 进程间切换的计算机资源开销很大,切换效率非常低
    • 进程间数据共享的开销也很大
    为什么进程之间切换资源开销大

    ​ OS是通过虚拟内存机制来实现进程空间独立的

    • 进程在并发运行时需要相互间的切换,切换时必然需要涉及虚拟内存机制的控制

    • 但是虚拟内存机制比较复杂,所以在进行进程间切换时,会耗费高昂的cpu、缓存(cache)、内存等计算机资源,也非常耗费切换时间

    进程通信的开销大

    ​ 当程序涉及多进程时,往往会涉及到进程间的通信,由于进程空间的独立性,OS提供了各种各样的通信机制:

    这些通信机制共同原理:通过OS来转发进程间的数据

    但是调用OS提供的这些通信机制的函数时,这些OS函数的运行也是需要消耗相当cpu、内存等计算机资源的,同时也很耗费时间


    对于有OS的计算机来说:

    虽然进程是必不可少的,但是进程确又不能太多,进程太多会导致计算机资源被剧烈消耗,此时你会发现你的计算机非常的卡

    创建多进程目的

    目的1:创建子进程,执行新程序

    目的2:创建子进程得到多进程,通过多进程并发实现多线任务

    多线任务例子
    1. 同时阻塞的读鼠标和键盘时,如果单线的话会想互影响,需要两线任务来实现

      比如:父进程读鼠标,子进程读键盘

    2. 读写管道时,读操作是阻塞的,为了避免读操作影响写操作,也需要两线任务同时进行

      父子进程一个读管道,一个写管道

    • 第一种目的,执行新程序时必须创建子进程,这个无法逃避的

    • 第二种目的,使用多进程来实现就存在巨大的问题

      因为几乎所有的程序都涉及多线任务的操作

    多线任务使用多进程的问题

    程序往往都是十几个任务以上,如果此时使用多进程来实现多线任务的,这就大致大量进程的产生

    比如计算机运行了100个程序,假设每个程序平均10多个任务,如果全部采用多进程来实现,计算机最终要运行的进程就多达上100个

    总结:

    进程切换和进程间通信的计算机资源开销又很大,往往导致计算机非常卡顿,程序的运行效率非常低

    线程的发明目的:弥补多进程实现多线任务的缺点。

    线程为什么可以弥补进程的缺点

    线程与进程一样,线程和进程会被OS统一调度,所以线程和进程都是一起并发运行的

    实现程序的多线任务的前提就是:并发执行

    有了线程以后,凡是程序涉及到多线任务时,都使用多线程来实现

    • 线程间的切换和数据通信的开销非常低
    • 线程还有另一个名称,叫“轻量级的进程”-----开销非常低

    说白了线程就是为了多线任务而生的

    为什么线程切换的开销低

    使用多线程来实现多线任务时:

    线程本质上它只是程序(进程)的一个函数,与普通函数的区别是:普通函数时单线的运行关系,而线程函数被注册为线程后,是多线并发运行。

    简单理解就是:
    1.普通函数的执行是:堆栈,也就是遇到子函数去执行子函数,再遇到子函数,切换到调用的子函数,执行完就返回到调用函数,直至结束,所以是单线
    2.而线程是并发的,不存在相互调用,只能时间片切换,所以是多线
    --
    线程函数也可以去调用普通函数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 对于普通函数来说,只有当相互调动时才会涉及函数间的切换
    • 对于线程函数来说,只要运行的时间片到了就会切换

    不管是函数间的切换,还是线程的切换都是进程内部的事情

    不涉及进程间的切换,故而省去了进程间切换的巨大开销

    但是两个线程分别存在于不同的进程,此时两个线程切换依然需要进程切换

    • 线程的切换其实就是函数间的切换,函数切换当然也需要开销,但是这些开销非常小
    为什么线程间数据共享的开销很低

    线程的本质就是函数,所以线程通信属于函数通信

    函数间通信有两种方式:
     (1)具有相互调用关系函数来说
    				         使用函数传参来通信。
     (2)对于没有调用关系的函数来说
    				         使用全局变量来通信。
    	               A函数 ————> 全局变量 ————> B函数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    全局变量的作用是什么?

    用来实现无调用关系的函数间通信


    进程中所有的线程函数除了相互并发运行外,没有调用关系

    所以线程函数间想要数据共享的话,就使用全局变量来通信


    线程为什么不能替代进程

    线程的本质是函数,函数运行需要内存空间,线程运行的内存空间就是进程的内存空间

    线程运行时必须依赖于进程的存在,如果没有进程所提供的内存空间这个资源,线程根本无法运行

    线程作为函数,只是进程的一个部分而已,线程是不可能脱离进程而独立存在

    • 同一个进程中的所有线程,都是运行在相同的进程空间中的
    • 换句话说同一个进程中所有线程共相同的进程空间

    既然进程空间是共享的,那么所有的函数,自然就能共享访问在共享空间中所开辟出来的全局变量

    线程独立的属性
    线程共享进程空间的资源
    • 全局变量
    • 工作目录(进程的当前工作目录)
    • 打开的文件描述符
    • 子函数
    • 进程uid、gid,进程PID等等
    线程的独立属性

    并发运行的就是一个单独执行体

    • 每个线程拥有自己独立的线程ID(TID)

    • 每个线程有独立的切换状态

      • 在切换时,当前线程被中断的那条指令的地址
      • 线程切换时相关的运行状态

      说白了就是:保存现场,便于还原

    • 有自己独立的函数栈

      每一个函数都有自己的函数栈,所有的函数栈都开辟于进程空间的进程栈

      函数栈的作用就是用来保存函数局部变量

    • 自己独立的错误号

      线程函数出错时,错误号并不是通过设置errno实现的,而是直接将错误号返回

    • 每一个线程有自己独立的信号屏蔽字未决信号集

    • 每个线程有自己独立的tack_struct结构体

      • 管理线程时:也会为线程开辟一个task_struct变量
      • 只不过适用于存放的是线程的管理信息

    创建子进程执行新程序,大多都是OS操心的事

    比如:父进程(命令行、图形界面)创建子进程并加载执行新程序

    但是我们自己的程序很少使用多进程,一般使用多线程

    线程控制函数

    • pthread_create()
    • pthread_join()
    • pthread_detach()
    • pthread_cancel()
    • pthread_exit等
    线程函数由谁提供

    进程控制的fork、exec等函数都是由os系统提供的

    为了不给OS增加负担,同时也为了提高线程的灵活性:

    后来的线程不由OS提供,而是由单独的线程库来提供

    不过线程库在实现时,也是调用了相应的系统API的

    线程库

    c线程库并不是C标准库,而是POSIX C库的一部分

    java、c++、c#的线程函数由他们自己的线程库来实现的

    • java、c++的线程函数会比c的线程复杂一些
    线程库和OS系统API的关系

    线程库函数实际上也是封住OS的相应API来实现的

    线程库运行在Linux,通过调用Linux的clone()等系统函数实现

    将线程函数注册为线程时:
    其实就是通过调用这类系统API,然后去模拟我们的进程来实现的
    正是因为是:模拟进程来实现的,所以线程函数才能进程一样,一起被并发运行
    
    • 1
    • 2
    • 3

    可以自己调用系统API,然后封装做出自己的c/c++/java线程库

    ----不过没啥价值和意义,因为有现成的

    C线程控制函数

    pthread_create()
    #include 
    //把第三个参数的函数注册成为一个线程函数
    //该函数一旦注册成功,这个函数就以次线程的方式开始并发运行
     int pthread_create(pthread_t *thread, const pthread_attr_t *attr,\
                         void *(*start_routine) (void *), void *arg);
    /*返回值
    		成功返回0,失败返回非零错误号*/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    Compile and link with -pthread

    目的主要是实现并发,而且该注册函数是次进程

    • 凡是使用pthread_create注册的线程都是次线程,次线程会和主线程一起并发运行。

    void* (*start_routine) (void *):参数和返回值都是void *,函数名就是地址

    参数
    1. thread------注意传指针,需要取地址

    2. attr----(attibute属性)

      是个重命名的结构体,用于设置线程属性

      • 设置线程属性是为了实现某些特殊功能
      • 设置为NULL,表示不设置特有的属性,使用线程默认属性所提供的功能

      正常情况下,线程默认属性所提供的功能就已经够用了

    3. 要注册为线程的函数地址

    如果不注册,线程函数就是一个普通的函数

    //线程函数需要我们自己定义
    void *pth_fun(void *pth_arg){
    		...//线程要做的事情
    		}
    //pth_fun和pth_arg的命名由自己决定。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. arg—传递给线程函数的参数

    传递给线程函数的参数,这个参数会传递给pth_arg

    • 如果参数很多的话,定义一个结构体,然后把结构体变量的地址传过去
    • 如果不传递参数,设置为NULL

    总结:pthread(进程号地址,设置属性(一般NULL),函数名地址,线程函数的传参)

    程序----两个次进程向文件写数据

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #define SECOND_PTH_NUMS 2
    void pth_print_err(char * str,int err){
        printf("%s:%s",str,strerror(err));
        exit(-1);
    }
    //次线程结构体参数
    typedef struct pthread_arg{
          pthread_t tid;
          int pth_no;//线程编号
          int fd;//打开的文件描述符
    }ptharg;
    //线程普通函数
    void *pth_fun(void *pth_arg){
        int fd=((ptharg *)pth_arg)->fd;
        while(1){
            printf("%d",((ptharg *)pth_arg)->pth_no);
            write(fd,"hello ",6);
            write(fd,"world\n",6);
            sleep(1);
        }
        return NULL;
    }
    int main(){
        int i,ret;
        int fd=open("./mmm.txt",O_CREAT|O_TRUNC|O_RDWR,0664);
        if(fd==-1) pth_print_err("open fails\n",errno);
        /*将函数注册为线程函数,传建两个次线程
            int pthread_create(线程号地址,设置属性,函数地址,线程函数传参);*/
        ptharg pth_arg[SECOND_PTH_NUMS];
        for(int i=0;i<SECOND_PTH_NUMS;i++){
            pth_arg[i].fd=fd;
            pth_arg[i].pth_no=i;
            ret= pthread_create(&pth_arg[i].tid,NULL,\
                                pth_fun,(void *)&pth_arg[i]);
            if(ret!=0){
                pth_print_err("pthread_create fails\n",ret);
            }
        }
        while(1){
            printf("father\n");
            sleep(2);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    在这里插入图片描述

    需要强调的地方
    • gcc编译时,跟-pthread选项(现在可以不加)

    • 父子线程调用子函数不冲突

      • main函数调用子函数时,子函数属于主线程这条线
      • 次线程调用子函数时,子函数属于次线程这条线。
    • 主线程代表了整个进程的存在

      • 主线程结束了,整个进程也就结束了,进程都没了线程自然也没了
      • 所以主线程一定不能死
      • 次线程结束了,对整个进程没有任何影响
    • C线程函数的启动与c++/java线程函数启动的略微有所不同

      • c++/java的线程函数被注册为线程后不会立即启动,需要单独调用某个启动函数来启动

      c线程函数一旦被pthread_create注册为线程后会立即被启动运行

      • c++、java等面向对象的函数,都被封装在类里面,包括线程函数也是如此

        而c这种面向过程语言的函数,全部都裸露在外的

    pthread_cancel()
    #include 
    //当次线程是死循环时,可以调动这个函数主动取消该线程
    //返回值:成功返回0,失败返回非零错误号
    int pthread_cancel(pthread_t thread);
    //参数------thread:要取消线程的TID
    //Compile and link with -pthread
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    //设置全局变量,供signal_fun使用
    typedef struct thread_args{
        pthread_t thrId;
        int thrNo;
        int fd;
    }threadArg;
    threadArg thr[2];
    //取消两个次线程(信号捕捉)
    void signal_fun(int signo){
        if(SIGALRM==signo){
            int i=0;
            for(;i<PTH_NUMS;i++)
                pthread_cancel(thr[i].thrId);
        }
    }
    //alarm,10秒后取消
     signal(SIGALRM,signal_fun);
     alarm(10);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    使用编号来结束

    好处是,完整执行程序后,再去判断是否该杀死

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #define PTH_NUMS 2
    #define PTHEXIT  -1
    typedef struct thread_args{
        pthread_t thrId;
        int thrNo;
        int fd;
    }threadArg;
    //结构体封装所有全局变量
    struct global_vals{
          threadArg thr[2];
          int pth_exit_flag[PTH_NUMS];
    }globalVal;//全局变量初始化为0
    void print_err(int err,char* str){
         printf("%s:%s",str,strerror(err));
         exit(-1);
    }
    void *pthread_fun(void *arg){
       int fd=((threadArg *)arg)->fd;
       int thrNo=((threadArg*)arg)->thrNo;
       pthread_t thrId=((threadArg *)arg)->thrId;
       /*线程功能*/
       while(1){
           printf("线程号:%lu,线程编号:%d\n",thrId,thrNo);
           write(fd,"hello,",6);
           write(fd,"world\n",6);
           if(globalVal.pth_exit_flag[thrNo]) break;
           sleep(1);
       }
       return NULL;
    }
    void signal_fun(int signo){
        if(SIGALRM==signo){
            int i=0;
            for(;i<PTH_NUMS;i++){
                globalVal.pth_exit_flag[i]=PTHEXIT;
                pthread_cancel(globalVal.thr[i].thrId);
            }
        }
    }
    int main(void){
        int fd=open("./threadFile",O_CREAT|O_TRUNC|O_RDWR,0664);
        int i,ret;
        for(i=0;i<PTH_NUMS;i++){
               globalVal.thr[i].fd=fd;
               globalVal.thr[i].thrNo=i;
               ret=pthread_create(&globalVal.thr[i].thrId,NULL,\
                         pthread_fun,(void *)&globalVal.thr[i]);
               if(ret!=0)
                   print_err(ret,"pthread_create fails\n");
            }
        //定时10s,时间到后取消次线程
        signal(SIGALRM,signal_fun);
        alarm(10);
        //父线程
        while(1){
            printf("father\n");
            sleep(2);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    pthread_exit()
    #include 
    //线程调用这个函数时,可以主动退出(终止)
    //返回值:成功返回0,失败返回非零错误号
    //参数----------retval:线程结束时的返回值。
    void pthread_exit(void *retval);
    /*如果返回值很多时,就封装成一个结构体,返回结构体变量的地址即可*/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 类似于exit函数,不过exit是终止整个进程的
    • pthread_exit是终止次线程

    事实上:线程也可以通过调动return来退出(终止)

    pthread_self()
    #include 
    //功能:线程获取自己的TID,类似于进程调用getpid()获取自己的PID一样
    pthread_t pthread_self(void);
    //返回值:成功返回线程TID,失败返回非零错误号。
    
    • 1
    • 2
    • 3
    • 4

    使用:

     printf("fatherTid:%lu\n",pthread_self());//主线程TID
    
    • 1
    pthread_join()
    #include 
    /*功能:
    阻塞等待tid为thread的次线程结束,结束时该函数会回收次线程所占用的所有资源(存储空间)
    */
    //返回值:成功返回0,失败返回错误号
    int pthread_join(pthread_t thread, void **retval);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这个函数只对次线程有意义,对主线程没有意义

    • 因为主线程结束时整个进程就结束了,整个进程资源会由父进程回收
    • 这个函数一般都是由主线程调用,以回收相关次线程的资源
    • 当然次线程也是可以调用这个函数来回收其它次线程资源的
    回收次线程资源原因
    • 有些程序(进程)一旦运行后将会长期运行,不会结束
    • 如果不回收,每结束一个次线程就导致一部分资源被占用
    • 慢慢累积会使得整个进程资源越用越少,最好导致进程崩溃

    说白了就是:次进程不断占据运行的进程的资源,最后过多而使得进程崩溃

    参数:
    (a)thread:指定要回收次线程的TID
    (b)retval:次线程函数返回的返回值

    ``参数二取的是void * 返回值的地址,所以是两个*`

    使用
     //次进程函数里面的返回值
    //return NULL;
       pthread_exit((void *)10);
    
    • 1
    • 2
    • 3
    //循环接收次进程资源(阻塞)
        void *retval=NULL;
        for(i=0;i<PTH_NUMS;i++){
            pthread_join(globalVal.thr[i].thrId,&retval);
            printf("%p\n",retval);
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    ​ 如果线程是被pthread_cancel取消掉的,自动返回-1

    pthread_detach()
    #include 
    /*功能:
    	如果次线程的资源不希望别人调用pthread_join函数来回收的话,而是希望自己在结束时,自动回收资源的话,就可以调用这个函数。
    */
    //返回值:成功返回0,失败返回错误号
    //参数:thread:你要分离的那个次线程的TID。
    int pthread_detach(pthread_t thread);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这个函数的功能就是分离次线程,让次线程在结束时自动回收资源

    使用:

     //次进程资源自动回收
       pthread_detach(globalVal.thr[i].thrId);
    
    • 1
    • 2
    • pthread_joinpthread_detach作为两种不同的线程资源回收方式,只能二选一
    注释代码的方式

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4gv5A57j-1669961351684)(/home/guojiawei/.config/Typora/typora-user-images/image-20221130184244462.png)]

    注册进程退出处理函数

    • 进程程在退出时会自动调用atexit();,实现进程的扫尾处理
    回顾进程处理函数:atexit
    void process_deal(){
        printf("\ndealing process!!\n");
    }
    //函数无返回值无传参
    
    • 1
    • 2
    • 3
    • 4
    /*进程处理函数*/
        atexit(process_deal);
    
    • 1
    • 2

    把ctrl+c变成正常退出

    void signal_fun(int signo){
     if(SIGALRM==signo){
         int i=0;
         for(;i<PTH_NUMS;i++){
             globalVal.pth_exit_flag[i]=PTHEXIT;
             pthread_cancel(globalVal.thr[i].thrId);
         }
     }
     else if(signo==SIGINT){
         exit(0);
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    signal(SIGINT,signal_fun);

    • 线程也是这样
     #include 
    //注册函数
    void pthread_cleanup_push(void (*routine)(void *),void *arg);
    void pthread_cleanup_pop(int execute);
    
    • 1
    • 2
    • 3
    • 4
    1. pthread_cleanup_push
    • 将类型为void (*routine)(void *)函数注册为“线程退出处理函数”

      注意第一个没*,第二个有*

    • arg:传递给退出处理函数的参数

    1. pthread_cleanup_pop

    执行这个函数时,参数:

    • 如果参数写!0

      会将压入栈中的推出处理函数地址弹出,然后调用退出函数进行线程的扫尾处理。

    • 如果参数写0

      不弹出调用

        注册的原理就是将处理函数地址压入线程栈
    ----可以反复调用该函数注册多个退出处理函数,但是一般一个就够了
    
    ----注册了多个线程退出处理函数的话,由于栈先进后出的特点
        注册压栈的顺序与弹栈调动的顺序刚好相反
    
    • 1
    • 2
    • 3
    • 4
    • 5

    有一个pthread_cleanup_push,就必须要对应有一个pthread_cleanup_pop

    就算这个函数调用不到也必须写,否则编译时不通过

    就像{}是成对出现的,缺一个都会报错

    例子
    /*线程退出处理函数*/
    void pth_exit_deal(void* t_arg){
      printf("pthread %lu exit\n",((threadArg*)t_arg)->thrId);
    }
    /*进程函数*/
    void *pthread_fun(void *arg){
        //注册 线程退出处理 函数
       pthread_cleanup_push(pth_exit_deal,arg);
       pthread_cleanup_pop(!0);
       pthread_exit((void *)10);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    弹栈线程退出处理函数的几种条件

    (a)调用thread_cleanup_pop(!0),主动弹栈

    (b)如果线程是被别人调用pthread_cancel取消的,也会弹栈

    (c)如果线程是调用pthread_exit函数退出的,也会弹栈

    如果线程时调用return退出的话,是不会自动弹栈的
    要弹栈的话,必须主动调动thread_cleanup_pop(!0)			
    
    • 1
    • 2

    线程的属性设置

    就是pthreadcreate()的第二个参数不要写NULL

    可以设置的线程属性
    • 设置绑定属性
    • 设置分离属性
    • 设置线程堆栈属性
    • 设置线程调度优先级属性

    事实上默认属性所提供的功能就已经足够我们使用了,

    C线程中设置属性的函数非常多,基本每一种属性都有独立的设置函数
    
    • 1
    例子:设置分离属性

    将线程分离有两种方法:

    • 调用pthread_detach函数实现

    • 通过设置分离属性实现

      事实上使用pthread_detach()更方便些

    分离属性设置的步骤:

    1. 定义一个变量来存放新属性

      pthread_attr_t attr;//一个结构体,被typedef重命名了
      
      • 1
    2. 调用pthread_attr_init(&attr);初始化一下attr结构体变量

      int pthread_attr_init(pthread_attr_t *attr);
      //成功返回0,失败返回错误号
      
      • 1
      • 2
    3. 调用pthread_attr_setdetachstate预设分离属性

      想设置什么类型属性就选择什么函数

      int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
      
      • 1
      pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
      
      • 1

      指定PTHREAD_CREATE_DETACHED宏后:

      pthread_attr_setdetachstate函数会自动的将:

      分离属性相关的数据设置到attr结构体变量的各个成员中

    4. 调动pthread_create创建线程时,将attr传递给pthread_create,将线程分离

    5. 删除属性设置

      int pthread_attr_destroy(pthread_attr_t *attr);
      //成功返回0,失败返回错误号
      
      • 1
      • 2
      #include 
      #include 
      #include 
      #include 
      #include 
      #include 
      #include 
      #include 
      #include 
      #include 
      #define PTH_NUMS 2
      #define PTHEXIT  -1
      typedef struct thread_args{
          pthread_t thrId;
          int thrNo;
          int fd;
      }threadArg;
      //结构体封装所有全局变量
      struct global_vals{
            threadArg thr[2];
            int pth_exit_flag[PTH_NUMS];
            pthread_attr_t attr;//设置线程属性
      }globalVal;//全局变量初始化为0
      void print_err(int err,char* str){
           printf("%s:%s",str,strerror(err));
           exit(-1);
      }
      /*线程退出处理函数*/
      void pth_exit_deal(void* t_arg){
        printf("pthread %lu exit\n",((threadArg*)t_arg)->thrId);
      }
      void *pthread_fun(void *arg){
         int fd=((threadArg *)arg)->fd;
         int thrNo=((threadArg*)arg)->thrNo;
         pthread_t thrId=((threadArg *)arg)->thrId;
         //pthread_detach(pthread_self());
         //注册 线程退出处理 函数
         pthread_cleanup_push(pth_exit_deal,arg);
         /*线程功能*/
         while(1){
             printf("线程号:%lu,线程编号:%d\n",thrId,thrNo);
             write(fd,"hello,",6);
             write(fd,"world\n",6);
             if(globalVal.pth_exit_flag[thrNo]) break;
             sleep(1);
         }
         //pthread_exit((void *)10);
         return NULL;
         pthread_cleanup_pop(!0);
      }
      void signal_fun(int signo){
          if(SIGALRM==signo){
              int i=0;
              for(;i<PTH_NUMS;i++){
                  globalVal.pth_exit_flag[i]=PTHEXIT;//退出标志
                  //pthread_cancel(globalVal.thr[i].thrId);//主动
              }
          }
          else if(signo==SIGINT){
              pthread_attr_destroy(&globalVal.attr);
              exit(0);
          }
      }
      void process_deal(){
          printf("\ndealing process!!\n");
      }
      int main(void){
          /*进程处理函数*/
          atexit(process_deal);
          int fd=open("./threadFile",O_CREAT|O_TRUNC|O_RDWR,0664);
          int i,ret;
          //属性初始化
          ret=pthread_attr_init(&globalVal.attr);
          if(ret!=0) print_err(ret,"attr fails\n");
          //设置分离属性
          pthread_attr_setdetachstate(&globalVal.attr,PTHREAD_CREATE_DETACHED);
          //线程初始化
          for(i=0;i<PTH_NUMS;i++){
                 globalVal.thr[i].fd=fd;
                 globalVal.thr[i].thrNo=i;
                 ret=pthread_create(&globalVal.thr[i].thrId,\
                                    &globalVal.attr,\
                           pthread_fun,(void *)&globalVal.thr[i]);
                  //次进程资源自动回收
                  pthread_detach(globalVal.thr[i].thrId);
                 if(ret!=0)
                     print_err(ret,"pthread_create fails\n");
              }
          //信号异常终止变成正常终止
          signal(SIGINT,signal_fun);
          //定时10s,时间到后取消次线程
          signal(SIGALRM,signal_fun);
          alarm(3);
          #if 0
          //循环接收次进程资源(阻塞)
          void *retval=NULL;
          for(i=0;i<PTH_NUMS;i++){
              pthread_join(globalVal.thr[i].thrId,&retval);
              printf("%ld\n",(long)retval);
          }
          #endif
          //父线程
          while(1){
              printf("fatherTid:%lu\n",pthread_self());//主线程TID
              sleep(2);
          }
          return 0;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
      • 78
      • 79
      • 80
      • 81
      • 82
      • 83
      • 84
      • 85
      • 86
      • 87
      • 88
      • 89
      • 90
      • 91
      • 92
      • 93
      • 94
      • 95
      • 96
      • 97
      • 98
      • 99
      • 100
      • 101
      • 102
      • 103
      • 104
      • 105
      • 106
      • 107
      • 108
  • 相关阅读:
    [工业自动化-8]:西门子S7-15xxx编程 - PLC主站 - CPU模块
    第20章 Netty
    大数据精准营销一站式解决你的获客难题
    CTF —— 网络安全大赛(这不比王者好玩吗?)
    携程apollo配置中心服务端如何感知配置更新?
    【k8s】三、k8s集群的初始化
    【正点原子FPGA连载】第九章 按键控制LED实验摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0
    jeecg中j-vxe-table和j-popup组件的修改使用
    在海思芯片上使用GDB远程调试
    vue-template-compiler的作用
  • 原文地址:https://blog.csdn.net/weixin_47173597/article/details/128147118