• volcanol的工控博客


    一、应用程序多线程

        当一个计算机上具有多个CPU核心的时候,每个CPU核心都可以执行代码,此时如果使用单线程,那么这个线程只能在一个
    CPU上运行,那么其他的CPU核心就处于空闲状态,浪费了系统资源;引入多线程可以解决这个问题,可以充分利用系统CPU
    的资源;  例如可以:线程2在CPU核心0上运行、线程2在CPU核心2上运行。
        又或者,当应用程序需要做一件查找很费时的操作,如果使用单线程,那么应用程序在处理这个“费时操作”的时候,就不能
    进行其他的操作,使用户等待操作处理过程,影响应用程序的实时性;利用多线程可以解决这个问题,应用程序可以让费时操作
    在一个线程中执行,而其他线程还可以处理其他任务,例如处理用户的操作等,这样应用程序的实时性和友好性都会提高。
     
    1、线程
        线程(thread)是操作系统能够进行运算调度的最小单位;线程被包含在进程中,是进程的实际执行单位。一条线程是指进程中
    一个单一顺序的控制流。
        进程中可以有多个线程,其中有一个线程称为主线程, 主线程执行完毕,那么应用程序也执行完毕。
     
    (1)线程创建
        在linux中,利用POSIX 线程库提供的创建线程的函数为,   pthread_create(); 其函数原型如下所示:
    SYNOPSIS
           #include <pthread.h>
    
           int pthread_create(pthread_t *restrict thread,    //标志线程的变量地址
                              const pthread_attr_t *restrict attr,  //创建的线程的属性
                              void *(*start_routine)(void*),        //线程回调处理函数
                              void *restrict arg                    //传递给线程回调函数的参数
                              );
    返回值:
            线程创建成功,返回0,
            线程创建失败,返回一个错误码,并设置全局错误码。
    (2) 线程的退出
        在linux中,POSIX标准的多线程退出函数为 pthread_exit( );其原型为:
    SYNOPSIS
           #include <pthread.h>
    
           void pthread_exit(void *value_ptr    //线程回调函数的返回值, 相当于线程回调函数的 return  的返回值。
                             );
     
    (3)线程的等待
        有时候,需要在创建线程的进程中等待线程的结束后,再进行进程的执行,这可以通过pthread_join( )函数实现,当在进程
    中调用 pthread_join( )函数的时候,那么进程就进入等待状态,等待线程执行完毕后,再往下执行pthread_join( )后面的代码。
        pthread_join( )函数的目的是为了在调用进程中释放被线程占用的资源。
    pthread_join( )函数的原型如下:
    SYNOPSIS
           #include <pthread.h>
    
           int pthread_join(pthread_t thread,   //调用进程需要等待的线程标志变量
                            void **value_ptr    //输出参数,如果整个参数不为空,那么就指向线程回调函数的返回值
                            );
    返回值:
            成功执行返回0
            执行失败返回一个错误码。
     
    (4)线程的分离
            利用 pthread_join( )函数来释放线程占用的系统资源需要等待线程返回,这样调用进程/创建进程 需要等待,执行效率很低,
    在实际应用中可以让线程自己释放资源,从而提高进程的执行效率。
            通过  pthread_detach( )函数使线程能自动释放系统资源, pthread_detach( )函数的原型是:
    SYNOPSIS
           #include <pthread.h>
    
           int pthread_detach(pthread_t thread    //要自动释放系统资源的线程的标志
                              );
    返回值:
            执行成功返回0, 失败返回错误码。
    要点:
            这个函数要使用的话,还需要其他一些机制,否则没法进行使用。需要在调用进程中将创建线程的属性设置为joinable状态,
    然后再在主进程中调用   pthread_detach( ) 。
        步骤:
        pthread_t thread_id;
        1、定义  pthread_attr_t attr; 变量
        2、初始化attr变量
                    pthread_attr_init(&attr) ;
        3、设置线程创建时的属性
                    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
        4、创建线程时设置attr属性
                    pthread_create(&thread_id, &attr, thread_handle, NULL);
     
     
    (5) 获取当前线程的标志符
        可以通过函数 pthread_self( ) 来获取当前线程的标志符,pthread_self( )的原型如下:
    SYNOPSIS
           #include <pthread.h>
    
           pthread_t pthread_self(void);
    返回值:
            成功返回当前线程的线程标识符。
            好像不会失败??
     
    (6)线程的撤销
        可以在一个线程中个另外线程发送消息,请求结束另一个线程的执行; pthread_cancel( )函数完成这个功能。接受到撤销
    请求的线程可以对“请求”推迟处理、忽略或者立即响应请求。
        pthread_cancel( )的原型如下:
    SYNOPSIS
           #include <pthread.h>
    
           int pthread_cancel(pthread_t thread    //接受请求的线程的标志
                                        );
     返回值:
            成功返回0; 失败返回错误码。
     
        与线程的撤销相关的函数有:pthread_setcancelstate( )、pthread_setcanceltype( ) 和 pthread_testcancel(),原型如下:
    SYNOPSIS
           #include <pthread.h>
    
           //pthread_setcancelstate用来设置线程的取消状态,禁止取消或者使能取消, 默认为使能的
           int pthread_setcancelstate(int state,    //线程要设置的状态
                                      int *oldstate //线程原来的状态
                                      );
            //pthread_setcanceltype函数用来设置线程的取消类型, 
           int pthread_setcanceltype(int type, 
                                     int *oldtype
                                                   );        
           //pthread_testcancel函数用来设置线程的撤销点
           void pthread_testcancel(void);
    返回值:
            成功返回0,失败返回错误码。
     
        需要描述几个概念:
            撤销状态:就是指线程是否响应撤销请求的状态,可以取值:PTHREAD_CANCEL_ENBALE 和 PTHREAD_CANCEL_DISABLE;
            ENABLE表示线程响应撤销请求, DISABLE表示线程不响应撤销请求。
            
            撤销类型: 撤销类型是指,如何响应撤销请求,有两种方式: 立即响应、等到线程执行到撤销点的时候,响应撤销。
                PTHREAD_CANCEL_ASYNCHRONUS 时表示立即响应撤销请求。
                PTHREAD_CANCEL_DEFERRED 使线程仅在几个取消点上响应取消请求。 默认为撤销点响应方式。
            
            撤销点: 当线程执行到撤销点的时候,就响应撤销请求。   利用 pthread_testcancel 函数设定撤销点。
     
    (7)测试多线程的创建和撤销
    main.c
    #include <stdio.h>
    #include <pthread.h>
    
    void* thread_test(void* argv)
    {
        int i;
        for(i=0;i<4;i++)
        {
            printf("in thread_test the i =%d.
    ",i);
        }
        return NULL;
    }
    
    int main(void)
    {
        int i;
        pthread_t pthread;
    
        //创建一个新的线程
        pthread_create(&pthread,NULL,thread_test,NULL);
        
        for(i=0;i<4;i++)
        {
            printf("in main function the i=%d.
    ",i);
        }
        return 0;
    }
    程序执行情况如下:
    [root@localhost thread]# gcc -lpthread main.c
    [root@localhost thread]# ./a.out
    in main function the i=0.
    in main function the i=1.
    in main function the i=2.
    in main function the i=3.
        可以发现创建的线程没有执行,如果要执行的话,那么必须设置线程资源的释放方式。
    ------------------------
        修改main.c 中的代码,然后进行测试,修改的代码如下:
    #include <stdio.h>
    #include <pthread.h>
    
    void* thread_test(void* argv)
    {
        int i;
        for(i=0;i<4;i++)
        {
            printf("in thread_test the i =%d.
    ",i);
        }
    
        return NULL;
    }
    
    int main(void)
    {
        int i;
        int* thread_val=NULL;
        pthread_t pthread;
    
        //创建一个新的线程
        pthread_create(&pthread,NULL,thread_test,NULL);
        pthread_join(pthread,(void *)&thread_val);   //增加线程资源释放方式
    
        for(i=0;i<4;i++)
        {
            printf("in main function the i=%d.
    ",i);
        }
        return 0;
    }
        执行结果如下:
    [root@localhost thread]# gcc -lpthread main.c
    [root@localhost thread]# ./a.out
    in thread_test the i =0.
    in thread_test the i =1.
    in thread_test the i =2.
    in thread_test the i =3.
    in main function the i=0.
    in main function the i=1.
    in main function the i=2.
    in main function the i=3.
        如上所示,线程回调函数thread_test()执行,而且是在等待线程回调函数 thead_test() 执行完毕后,才执行
    pthread_join(&pthread);  这后一句的代码。
    --------------------------
        再次修改main.c 的代码,如下:
         //创建一个新的线程
        pthread_create(&pthread,NULL,thread_test,NULL);
                                                          //这里的语句放到下面了
        for(i=0;i<4;i++)
        {
            printf("in main function the i=%d.
    ",i);
            pthread_join(pthread,(void *)&thread_val);      //将设定线程资源释放方式函数放到这里
        }
          执行结果如下:
    [root@localhost thread]# gcc -lpthread main.c
    [root@localhost thread]# ./a.out
    in main function the i=0.
    in thread_test the i =0.
    in thread_test the i =1.
    in thread_test the i =2.
    in thread_test the i =3.
    in main function the i=1.
    in main function the i=2.
    in main function the i=3.
        可以发现,线程是在调用 pthread_join(pthread,(void *)&thread_val) ; 函数后开始执行,执行完毕后
    再执行主调函数main中其余的代码。
    -----------------
    下面测试非等待模式的线程, 修改后的main.c如下所示:
    #include <stdio.h>
    #include <pthread.h>
    
    void* thread_test(void* argv)
    {
        int i;
    
        for(i=0;i<4;i++)
        {
            printf("in thread_test the i =%d.
    ",i);
            sleep(1);
        }
    
        pthread_exit((void*)1);
    }
    
    int main(void)
    {
        unsigned int i;
        int* thread_val=NULL;
        pthread_t pthread;
      
       if( pthread_create(&pthread,NULL,thread_test,(void*)&thread_val) )
        {
            perror("pthread create");
        }
        if(pthread_detach(pthread))
        {
            perror("pthread detach");
        }
    
        for(i=0;i<100000000;i++)
        {
            printf("in main function the i=%d.
    ",i);
        }
        return 0;
    }
        要点:
                在多线程同时执行的时候,不能保证那个线程先执行,因此这个地方需要保证主线程结束之前,其他线程有
    机会执行。因此这里 i 的值需要设定一个很大的值。
        为了查看是否调用了 线程回调函数,可以利用下面命令进行测试:
    [root@localhost thread]# gcc main.c -lpthread
    [root@localhost thread]# ./a.out | grep test
    in thread_test the i =0.
    in thread_test the i =1.
    in thread_test the i =2.
    in thread_test the i =3.
            为了是测试结果更加直观,所以修改一下代码,在主线程和创建的线程都调用sleep()让线程暂时休眠。
    修改后的main.c
    [root@localhost thread]# cat main.c
    #include <stdio.h>
    #include <pthread.h>
    
    void* thread_test(void* argv)
    {
        int i;
    
        for(i=0;i<4;i++)
        {
            printf("in thread_test the i =%d.
    ",i);
            sleep(1);
        }
    
        pthread_exit((void*)1);
    }
    
    int main(void)
    {
        unsigned int i;
        int* thread_val=NULL;
        pthread_t pthread;
    
        //创建一个新的线程
        if( pthread_create(&pthread,NULL,thread_test,(void*)&thread_val) )
        {
            perror("pthread create");
        }
        if(pthread_detach(pthread))
        {
            perror("pthread detach");
        }
    
        for(i=0;i<5;i++)
        {
            printf("in main function the i=%d.
    ",i);
            sleep(1);
        }
    
        return 0;
    }
    执行结果如下:
    [root@localhost thread]# ./a.out
    in main function the i=0.
    in thread_test the i =0.            //可以发现线程交替执行
    in main function the i=1.
    in thread_test the i =1.
    in main function the i=2.
    in thread_test the i =2.
    in main function the i=3.
    in thread_test the i =3.
    in main function the i=4.
        再将代码修改如下:
    root@localhost thread]# cat main.c
    #include <stdio.h>
    #include <pthread.h>
    
    void* thread_test(void* argv)
    {
        int i;
        pthread_detach(pthread_self());    //通过pthread_self()获取本线程的线程标志
        for(i=0;i<4;i++)
        {
            printf("in thread_test the i =%d.
    ",i);
            sleep(1);
        }
        pthread_exit((void*)1);
    }
    
    int main(void)
    {
        unsigned int i;
        int* thread_val=NULL;
        pthread_t pthread;
        //创建一个新的线程
        if(pthread_create(&pthread,NULL,thread_test,NULL))
        {
           perror("pthread create");
        }
        for(i=0;i<5;i++)
        {
            printf("in main function the i=%d.
    ",i);
            sleep(1);
        }
        return 0;
    }
    执行结果如下:
    [root@localhost thread]# ./a.out
    in main function the i=0.
    in thread_test the i =0.
    in main function the i=1.
    in thread_test the i =1.
    in main function the i=2.
    in thread_test the i =2.
    in main function the i=3.
    in thread_test the i =3.
    in main function the i=4.
        可以看出,可以在线程里面自动释放资源。
     
     
    2、应用程序线程间的同步
        当多个线程范文共享数据时,可能产生冲突,为了解决访问冲突的问题,引入互斥锁机制,获得锁的线程可以进行相应的访问,
    获得锁的线程在访问完成之后,就将锁释放给其他的线程,没有获得锁的线程只能等待而不能进行访问操作。
        互斥量是一种特殊的变量,可以处于锁定状态(locked),也可以处于解锁状态(unlocked), 要使用互斥锁需要经过以下几步:
        A: 创建互斥锁变量
        B:初始化互斥锁变量
        C: 线程获得互斥锁
        D:  获得锁的线程执行加锁和解锁之间的代码, 可有获得互斥锁的线程进行等待。
        E: 线程是否互斥锁, 等待加锁的线程获得互斥锁,然后执行上面一样的操作。
     
    (1)创建并初始化互斥量
        linux中使用 pthread_mutex_t 类型的变量来表示互斥锁, 程序在用pthread_mutex_t 类型的变量进行同步控制之前
    必须初始化互斥量。
        有两种初始化互斥量的方法:  静态初始化互斥量、动态初始化互斥量。静态初始化使用互斥量初始化器对互斥量初始化,
    动态初始化调用初始化函数实现。
    SYNOPSIS
           #include <pthread.h>
    
           int pthread_mutex_init(pthread_mutex_t *restrict mutex,   //待初始化互斥量的指针
                                  const pthread_mutexattr_t *restrict attr   //待初始化互斥量要设置的属性
                                                );
           pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;    //静态初始化互斥量
     
    返回值:
            初始化成功,返回0, 失败返回错误码。
    (2)销毁互斥量的初始化状态
        被销毁的互斥量可以再次被初始化函数进行动态初始化, 互斥量销毁初始化状态的函数为:  pthread_mutex_destroy,
    函数的原型为:
      int pthread_mutex_destroy(pthread_mutex_t *mutex   //要销毁初始化状态互斥量的指针
                                 );
    返回值:
            成功返回0, 失败返回错误码。
     
    (3)获取互斥锁
            通过函数  pthread_mutex_lock()或者 pthread_mutex_trylock( ) 函数获取互斥锁。两个函数的区别是,如果不能获取互斥锁
    那么调用 pthread_mutex_lock()函数获取互斥锁的线程就就进入等待状态; 而调用 pthread_mutex_trylock()函数就理解返回。
    SYNOPSIS
           #include <pthread.h>
    
           int pthread_mutex_lock(pthread_mutex_t *mutex    //要获取的互斥锁的指针
                                 );
           int pthread_mutex_trylock(pthread_mutex_t *mutex   //要获取的互斥锁的指针
                                                   );
     返回值:
                pthread_mutex_lock ,成功返回0, 失败返回错误码。
                pthread_mutex_trylock失败返回EBUSY。
     
    (4)释放互斥锁
            通过 pthread_mutex_unlock()函数释放互斥锁,函数原型如下
    int pthread_mutex_unlock(pthread_mutex_t *mutex     //要释放的互斥锁的指针
                                  );
    返回值:
            成功返回0,失败返回错误码。
     
     Exp:
            首先测试 采用线程互斥锁  pthread-mutex.c
    #include <stdio.h>
    #include <pthread.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdlib.h>
    
    typedef struct {
        int fd;
        pthread_mutex_t p_mutex;
    }p_fd_t;
    
    void* thread_test(void* argv)
    {
        int i;
        int j;
        int size;
        char buf[256];
        p_fd_t* fd_mutex;
    
        pthread_detach(pthread_self());
    
        //将传递进来的参数变为结构题指针
        fd_mutex=(p_fd_t *)argv;
    
        //加锁
        pthread_mutex_lock(&fd_mutex->p_mutex);
        for(i=0;i<5;i++)
        {
            j=0;
            size=sprintf(buf,"the pthred id is: %d
    ",pthread_self());
            while(j<size)
            {
                write(fd_mutex->fd,&buf[j++],1);
                usleep(1);
            }
        }
        //解锁
        pthread_mutex_unlock(&fd_mutex->p_mutex);
    
        pthread_exit((void*)0);
    }
    
    int main(void)
    {
        unsigned int i;
        int j;
        int size;
        int fd;  
        char buf[256];
        pthread_t pthread;
        p_fd_t  fd_mutex=
        {
            .p_mutex=PTHREAD_MUTEX_INITIALIZER, //初始化线程互斥锁
        };
    
        //打开或者创建一个文件
        fd=open("./test",O_RDWR | O_CREAT | O_TRUNC, 0666);
        if(-1 == fd)
        {
            perror("open ./test"); 
            exit(1);
        }
    
        fd_mutex.fd=fd;
        //创建一个新的线程
        if(pthread_create(&pthread,NULL,thread_test,(void*)&fd_mutex))
        {
           perror("pthread create");
        }
        
        //加锁
        pthread_mutex_lock(&fd_mutex.p_mutex);
        for(i=0;i<5;i++)
        {
            j=0;
            size=sprintf(buf,"the main pthred id is: %d
    ",pthread_self());
            while(j<size)
            {
                write(fd_mutex.fd,&buf[j++],1);
                usleep(1);
            }
        }
        //解锁
        pthread_mutex_unlock(&fd_mutex.p_mutex);
    
        sleep(1);  //加上时间等待 线程结束
        //在主线程结束之间销毁线程互斥锁资源
        pthread_mutex_destroy(&fd_mutex.p_mutex);
        close(fd);
    
        return 0;
    }
        测试后生成的 test 文件的内容如下:
    [root@localhost thread]# cat test 
    the main pthred id is: -1208031552
    the main pthred id is: -1208031552
    the main pthred id is: -1208031552
    the main pthred id is: -1208031552
    the main pthred id is: -1208031552       //前面的是主线程写入文件的内容,后面是线程写入到内容
    the pthred id is: -1208034416
    the pthred id is: -1208034416
    the pthred id is: -1208034416
    the pthred id is: -1208034416
    the pthred id is: -1208034416
    [root@localhost thread]# 
     
        将锁机制取消后的 pthread-mutex.c 如下:
    #include <stdio.h>
    #include <pthread.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdlib.h>
    
    typedef struct {
        int fd;
        pthread_mutex_t p_mutex;
    }p_fd_t;
    
    void* thread_test(void* argv)
    {
        int i;
        int j;
        int size;
        char buf[256];
        p_fd_t* fd_mutex;
    
        pthread_detach(pthread_self());
    
        //将传递进来的参数变为结构题指针
        fd_mutex=(p_fd_t *)argv;
    
        //加锁
        /*pthread_mutex_lock(&fd_mutex->p_mutex);*/
        for(i=0;i<5;i++)
        {
            j=0;
            size=sprintf(buf,"the pthred id is: %d
    ",pthread_self());
            while(j<size)
            {
                write(fd_mutex->fd,&buf[j++],1);
                usleep(1);
            }
        }
        //解锁
        /*pthread_mutex_unlock(&fd_mutex->p_mutex);*/
    
    
        pthread_exit((void*)0);
    }
    
    int main(void)
    {
        unsigned int i;
        int j;
        int size;
        int fd;  
        char buf[256];
        pthread_t pthread;
        p_fd_t  fd_mutex=
        {
            .p_mutex=PTHREAD_MUTEX_INITIALIZER,   //初始化线程互斥锁
        };
    
        //打开或者创建一个文件
        fd=open("./test",O_RDWR | O_CREAT | O_TRUNC, 0666);
        if(-1 == fd)
        {
            perror("open ./test"); 
            exit(1);
        }
    
        fd_mutex.fd=fd;
        //创建一个新的线程
        if(pthread_create(&pthread,NULL,thread_test,(void*)&fd_mutex))
        {
           perror("pthread create");
        }
        
        //加锁
        /*pthread_mutex_lock(&fd_mutex.p_mutex);*/
        for(i=0;i<5;i++)
        {
            j=0;
            size=sprintf(buf,"the main pthred id is: %d
    ",pthread_self());
            while(j<size)
            {
                write(fd_mutex.fd,&buf[j++],1);
                usleep(1);
            }
        }
        //解锁
        /*pthread_mutex_unlock(&fd_mutex.p_mutex);*/
    
        sleep(1);  //加上时间等待 线程结束
        //在主线程结束之间销毁线程互斥锁资源
        pthread_mutex_destroy(&fd_mutex.p_mutex);
        close(fd);
    
        return 0;
    }
        执行后,生成的 test 文件内容如下
    [root@localhost thread]# vim pthread.mutex.c 
    [root@localhost thread]# gcc pthread.mutex.c  -lpthread
    [root@localhost thread]# ./a.out 
    [root@localhost thread]# cat test 
    tthhee m apitnh prtehdr eidd  iids :i s-:1 2-0182505845650187
    44t
    thhee  mapitnh rpetdh riedd i di sis::  --112200885551574446
    0t8h
    e tmhaein  pptthhrreedd  iidd  iiss::  --11220088555541670484
    
    tthhee  mpatihnr epdt hirded  iisd:  i-s1:2 0-182505845650187
    4t4
    hteh ep tmhariend  pitdh riesd:  i-d1 2i0s8:5 5-416200885
    51744
    [root@localhost thread]# 
        两次生成文件的内容,不加锁时与加锁时的不一样,不加锁是一堆乱码。
       
      【Linux草鞋应用编程】_4_应用程序多线程
       本系列文章欢迎批评指正,包括概念上,或是输入错误等。
      文章继续连载,未完待续......
  • 相关阅读:
    软件设计分层
    技术填补
    崩溃预防
    架构前期准备
    系统重构
    如何保证架构的质量(稳定性和健壮性)
    你知道有哪些架构师吗
    12 koarouter
    11 Koa起步
    Hadoop源码分析hdfs(1)启动脚本分析
  • 原文地址:https://www.cnblogs.com/volcanol/p/3475247.html
Copyright © 2020-2023  润新知