zoukankan      html  css  js  c++  java
  • 《嵌入式linux应用程序开发标准教程》笔记——9.多线程编程

      线程是轻量级进程,创建线程的开销要比进程小得多,在大型程序中应用广泛。

    9.1 线程概述

    • 进程包含自己的代码、数据、堆栈、资源等等,创建和切换的开销比较大;
    • 线程是轻量级的进程,调度的最小单元,同一个进程内的线程可以共享资源;
    • 线程的上下文开销比进程小得多;
    • 线程有自己的堆栈,但是用户空间共享,例如一个线程修改全局变量,会影响到同一个进程内的另一个线程;

    • linux里其实线程就是轻量级的进程,都用PCB表示,只不过新建线程时,共享同一个进程内的资源。线程应该是单独调度

    9.2 线程编程

      线程一般在用户空间操作,pthread线程库是通用的POXIS标准。

    9.2.1 函数说明及示例

    •  创建线程:pthread_create(),  确定线程的入口函数,创建后就开始执行;
    •  退出线程:方式1,入口函数执行完后以后,自动结束;方式2,主动退出,pthead_exit().
    •  释放资源:pthread_join(),类似wait(),也是阻塞的
    •  终止别的进程:pthread_cancel(),终止别的线程;pthread_setcancel()/pthread_setcanceltype()设置是否允许别的线程结束自己及结束的方式等

    【注意】:

    • 线程如果调用exit(),会使整个进程退出。
    #include <pthread.h>

    int pthread_create( pthread_t * thread, pthread_attr_t * attr, void *(*start_routine)(void *), void * arg );
    参数:
      thread,线程标识符
      attr,线程属性
      start_routine, 线程入口函数
      arg,传递给入口函数的参数
    返回值:
      成功:0
      出错:返回错误码

    void pthread_exit( void * retval );
    参数:
      retval,线程结束时的返回值,由其他线程使用pthread_join()获取
      【注意】:如果返回变量,不用使用线程的局部变量,因为推出以后,线程的堆栈空间可能被占用,导致返回值的变化。 可以返回绝对值或者变量值。

    int pthread_join( pthread_t thread, void ** thread_return );
    参数:
      thread,要等待的线程标识符
      thread_return, 被等待线程的返回值,实质是把 *thread_return = retval,其中retval是pthread_exit的指针,即传递了一个地址而已。可以利用传递的地址直接传递返回值,也可以利用变量传递,详见后面用例。
    返回值:
      成功:0
      出错:返回错误码

    int pthread_cancel( pthread_t thread )
    参数:
      thread,要取消的线程标识符
    返回值:
      成功:0
      出错:返回错误码

    //#define EXIT_USE_VER // 用变量返回,不可用线程自己的局部变量,因为退出以后有可能被复用

    #define EXIT_USE_POINT // 用指针的赋值返回,适用于返回绝对值

    #include <stdio.h> // printf
    #include <stdlib.h> // exit
    #include <unistd.h>
    #include <sys/types.h> // pid_t
    #include <fcntl.h>
    #include <pthread.h>

    #define MAX_DELAY_SECONDS 10.0
    int rtn[3];

    void fun_thread( int thread_no )
    {
      unsigned char count=0;
      int delay_time=0;

      printf("thread %d start. ",thread_no);
      for(count=0;count<3;count++)
      {
        delay_time = (int)(rand() * MAX_DELAY_SECONDS/RAND_MAX)+1;
        sleep(delay_time);
        printf("thread %d loop %dst delay second %d ",thread_no,count,delay_time);
      }
    #ifdef EXIT_USE_VER
      rtn[thread_no] = thread_no;
      pthread_exit((void*)&rtn[thread_no]); // 不同线程退出值不同,仅测试用
    #endif
    #ifdef EXIT_USE_POINT
      pthread_exit((void*)0xaa); // 可以返回绝对值
    #endif
    }

    int main(int args, char *argv[])
    {
      unsigned thread_no;
      pthread_t thread[3];
    #ifdef EXIT_USE_VER
      int * thread_rtn;
    #endif
    #ifdef EXIT_USE_POINT
      void * thread_rtn;
    #endif

      printf("start. ");
      for( thread_no=0; thread_no<3; thread_no++ )
      {
        if( pthread_create(&thread[thread_no],NULL,fun_thread,(void*)thread_no) <0 )
        printf("Create thread %d err. ",thread_no);
      }

      // exit(0); 不能有此函数,否则创建完线程以后,exit会把本进程及内部所有线程一并退出

      // 方法1:等待输入后结束
      // getchar();

      // 方法2:用pthread_join()等待所有线程结束
      for( thread_no=0; thread_no<3; thread_no++ )
      {
    #ifdef EXIT_USE_VER
        if( pthread_join(thread[thread_no], (void**)&thread_rtn) < 0 )
    #endif
    #ifdef EXIT_USE_POINT
        if( pthread_join(thread[thread_no], &thread_rtn) < 0 )
    #endif
          printf( "pthread_join:thread %d err. ", thread_no);
        else
    #ifdef EXIT_USE_VER
          printf( "pthread_join:thread %d status %d. ", thread_no,*thread_rtn);
    #endif
    #ifdef EXIT_USE_POINT
          printf( "pthread_join:thread %d status %d. ", thread_no,(int)thread_rtn);
    #endif
      }

      exit(0);
    }

    $ ./example
    start.
    thread 2 start.
    thread 1 start.
    thread 0 start.
    thread 1 loop 0st delay second 4
    thread 0 loop 0st delay second 8
    thread 2 loop 0st delay second 9
    thread 2 loop 1st delay second 2
    thread 1 loop 1st delay second 8
    thread 2 loop 2st delay second 4
    thread 0 loop 1st delay second 10
    thread 1 loop 2st delay second 8
    thread 0 loop 2st delay second 3
    pthread_join:thread 0 status 170.
    pthread_join:thread 1 status 170.
    pthread_join:thread 2 status 170.

    9.2.2 线程之间的同步和互斥

      互斥锁适用于只有1个共享资源;

      信号量适用多个共享资源的同步。

    9.2.2.1 互斥锁线程控制

    •  互斥锁可简单理解成全局变量,只有上锁和解锁两种状态;
    •  若干其他线程希望上锁一个已经被上锁的互斥锁,则该线程挂起
    •  相关函数:
      • pthread_mutex_init(): 初始化
      • pthread_mutex_lock():上锁
      • pthread_mutex_trylock():判断上锁
      • pthread_mutex_unlock():解锁
      • pthread_mutex_destroy():清除
    #include <pthread.h>

    int pthread_mutex_init( pthread_mutex_t * mutex, const pthread_mutexattr_t * mutexattr );
    参数:
      mutex,互斥锁;
      mutexattr,互斥锁种类,若为NULL,则使用默认的互斥锁类型。具体的锁类型后续研究吧
    返回值:
      成功:0
      出错:返回错误码

    int pthread_mutex_lock( pthread_mutex_t * mutex );
    int phtread_mutex_trylock( pthread_mutex_t * mutex );
    int pthread_mutex_unlock( pthread_mutex_t * mutex );
    int pthread_mutex_unlock( pthread_mutex_t * mutex );
    参数:
      mutex,互斥锁
    返回值:
      成功:0
      出错:-1

    增加互斥锁,使原本独立的无序程序按设计预期运行

    /* 9-1,pthread_mutex */

    //#define EXIT_USE_VER // 用变量返回,不可用线程自己的局部变量,因为退出以后有可能被复用
    #define EXIT_USE_POINT // 用指针的赋值返回,适用于返回绝对值

    #include <stdio.h> // printf
    #include <stdlib.h> // exit
    #include <unistd.h>
    #include <sys/types.h> // pid_t
    #include <fcntl.h>
    #include <pthread.h>

    #define MAX_DELAY_SECONDS 10.0

    #ifdef EXIT_USE_VER
    int rtn[3];
    #endif

    pthread_mutex_t g_mutex;

    void fun_thread( int thread_no )
    {
      unsigned char count=0;
      int delay_time=0;

      pthread_mutex_lock(&g_mutex);
      printf("thread %d start. ",thread_no);
      for(count=0;count<3;count++)
      {
        delay_time = (int)(rand() * MAX_DELAY_SECONDS/RAND_MAX)+1;
        sleep(delay_time);
        printf("thread %d loop %dst delay second %d ",thread_no,count,delay_time);
      }
      pthread_mutex_unlock(&g_mutex);
    #ifdef EXIT_USE_VER
      rtn[thread_no] = thread_no;
      pthread_exit((void*)&rtn[thread_no]); // 不同线程退出值不同,仅测试用
    #endif
    #ifdef EXIT_USE_POINT
      pthread_exit((void*)0); // 可以返回绝对值
    #endif
    }

    int main(int args, char *argv[])
    {
      unsigned thread_no;
      pthread_t thread[3];
    #ifdef EXIT_USE_VER
      int * thread_rtn;
    #endif
    #ifdef EXIT_USE_POINT
      void * thread_rtn;
    #endif

      pthread_mutex_init(&g_mutex,NULL);

      printf("start. ");
      for( thread_no=0; thread_no<3; thread_no++ )
      {
        if( pthread_create(&thread[thread_no],NULL,fun_thread,(void*)thread_no) <0 )
        printf("Create thread %d err. ",thread_no);
      }

      // exit(0); 不能有此函数,否则创建完线程以后,exit会把本进程及内部所有线程一并退出

      // 方法1:等待输入后结束
      // getchar();

      // 方法2:用pthread_join()等待所有线程结束
      for( thread_no=0; thread_no<3; thread_no++ )
      {
    #ifdef EXIT_USE_VER
        if( pthread_join(thread[thread_no], (void**)&thread_rtn) < 0 )
    #endif
    #ifdef EXIT_USE_POINT
        if( pthread_join(thread[thread_no], &thread_rtn) < 0 )
    #endif
          printf( "pthread_join:thread %d err. ", thread_no);
        else
    #ifdef EXIT_USE_VER
          printf( "pthread_join:thread %d status %d. ", thread_no,*thread_rtn);
    #endif
    #ifdef EXIT_USE_POINT
          printf( "pthread_join:thread %d status %d. ", thread_no,(int)thread_rtn);
    #endif

      }

      pthread_mutex_destroy(&g_mutex);
      exit(0);
    }

    $ ./example
    start.
    thread 2 start.
    thread 2 loop 0st delay second 9
    thread 2 loop 1st delay second 4
    thread 2 loop 2st delay second 8
    thread 1 start.
    thread 1 loop 0st delay second 8
    thread 1 loop 1st delay second 10
    thread 1 loop 2st delay second 2
    thread 0 start.
    thread 0 loop 0st delay second 4
    thread 0 loop 1st delay second 8
    thread 0 loop 2st delay second 3
    pthread_join:thread 0 status 0.
    pthread_join:thread 1 status 0.
    pthread_join:thread 2 status 0.

    9.2.2.2 信号量线程控制

    •  sem,本质上是非负的整数计数器
    •  P:sem-1
    •  V: sem+1
    • sem>=0,进程/线程具有公共资源的访问权
    • sem<0,进程/线程没有公共资源访问权,阻塞,指导sem>=0为止
    • 用途:同步和互斥,若互斥,往往只有1个sem;同步时,往往有多个sem,安排不同初值来实现顺序执行
    • 线程和进程的sem接口函数不同

             互斥流程

    #include <semaphore.h>

    int sem_init( sem_t *sem, int pshared, unsigned int value );  // 创建信号量,并初始化它的值
    参数:
      sem,信号量指针
      pshared,决定信号量能在几个进程间共享,linux不支持,只能用0;
      value,信号量初始值
    返回值:
      成功:0
      出错:-1

    int sem_wait( sem_t *sem );    // P操作,阻塞
    int sem_trywait( sem_t *sem );  // P操作,不阻塞
    int sem_post( sem_t *sem );    // V操作
    int sem_getvalue(set_t * sem ); // 得到信号量的值
    int sem_destroy(sem_t * sem );  // 删除信号量
    参数:
      sem,信号量指针
    返回值:
      成功:0
      出错:-1

     用信号量实现3个进程的有序执行,倒着来,2->1->0

    /* 9-3,sem */

    #include <stdio.h> // printf
    #include <stdlib.h> // exit
    #include <unistd.h>
    #include <sys/types.h> // pid_t
    #include <fcntl.h>
    #include <pthread.h>
    #include <semaphore.h>

    #define MAX_DELAY_SECONDS 2.0

    sem_t g_sem[3];

    void fun_thread( int thread_no )
    {
      unsigned char count=0;
      int delay_time=0;

      sem_wait(&g_sem[thread_no]);

      printf("thread %d start. ",thread_no);
      for(count=0;count<3;count++)
      {
        delay_time = (int)(rand() * MAX_DELAY_SECONDS/RAND_MAX)+1;
        sleep(delay_time);
        printf("thread %d loop %dst delay second %d ",thread_no,count,delay_time);
      }

      if( thread_no!=0 )
        sem_post(&g_sem[thread_no-1]);
      pthread_exit((void*)0); // 可以返回绝对值
    }

    int main(int args, char *argv[])
    {
      unsigned thread_no;
      pthread_t thread[3];
      void * thread_rtn;

      printf("start. ");

      sem_init(&g_sem[0], 0,0);
      sem_init(&g_sem[1], 0,0);
      sem_init(&g_sem[2], 0,1);  // 允许2先运行


      for( thread_no=0; thread_no<3; thread_no++ )
      {
        if( pthread_create(&thread[thread_no],NULL,fun_thread,(void*)thread_no) <0 )
        printf("Create thread %d err. ",thread_no);
      }

      // exit(0); 不能有此函数,否则创建完线程以后,exit会把本进程及内部所有线程一并退出

      // 方法1:等待输入后结束
      // getchar();

      // 方法2:用pthread_join()等待所有线程结束
      for( thread_no=0; thread_no<3; thread_no++ )
      {
      if( pthread_join(thread[thread_no], &thread_rtn) < 0 )
        printf( "pthread_join:thread %d err. ", thread_no);
      else
        printf( "pthread_join:thread %d status %d. ", thread_no,(int)thread_rtn);
      }
      sem_destroy(&g_sem[0]);
      sem_destroy(&g_sem[1]);
      sem_destroy(&g_sem[2]);

      exit(0);
    }

    $ ./example
    start.
    thread 2 start.
    thread 2 loop 0st delay second 2
    thread 2 loop 1st delay second 1
    thread 2 loop 2st delay second 2
    thread 1 start.
    thread 1 loop 0st delay second 2
    thread 1 loop 1st delay second 2
    thread 1 loop 2st delay second 1
    thread 0 start.
    thread 0 loop 0st delay second 1
    thread 0 loop 1st delay second 2
    thread 0 loop 2st delay second 1
    pthread_join:thread 0 status 0.
    pthread_join:thread 1 status 0.
    pthread_join:thread 2 status 0.

    9.2.3 线程属性

    • pthread_create()的第二个参数可以设定线程属性,包括绑定、分离、堆栈、优先级等;
    • 绑定属性,内核线程与用户线程一对一,linux用内核线程调度,如果绑定,能够保证需要时总能有对应的内核线程;如果不绑定,需要时系统分配内核线程;比较重要的线程倒是可以考虑设置绑定属性
    • 分离属性,默认非分离,线程结束也留有一定的资源,pthread_join以后才彻底释放;分离,执行结束马上释放,有问题,例如执行很快,可能在create之前线程就结束返回了,此时连线程号都释放了,可能导致出错。完全没必要设置分离属性。
    #include <pthread.h>

    int pthread_attr_init( pthread_attr_t * attr );    // 初始化
    参数:
      attr,线程属性
    返回值:
      成功:0
      出错:返回错误码

    int pthread_attr_setscope( pthread_attr_t * attr, int scope );  // 是否绑定
    参数:
      attr,线程属性
      scope,PTHREAD_SCOPE_SYSTEM:绑定
         PTHREAD_SCOPE_PROCESS:非绑定
    返回值:
      成功:0
      出错:-1
    int pthread_attr_setdetachstate( pthread_attr_t * attr, int detachstate );  // 是否分离
    参数:
      attr,线程属性
      scope,PTHREAD_CREATE_DETACHED:分离
         PTHREAD_SCOPE_JOINABLE:非分离
    返回值:
      成功:0
      出错:-1

    int pthread_attr_getschedparam( pthread_attr_t * attr, struct sched_param * param );  // 是否分离
    参数:
      attr,线程属性
      param,线程优先级
    返回值:
      成功:0
      出错:-1
    int pthread_attr_setschedparam( pthread_attr_t * attr, struct sched_param * param );  // 是否分离
    参数:
      attr,线程属性
      param,线程优先级
    返回值:
      成功:0
      出错:-1
  • 相关阅读:
    Oracle数据库安装
    [转]卡西欧手表调日期正确方法
    python密码处理(可用于生产模式)
    [转]python对json的相关操作
    [转]Python中的with…as…
    Python标准库--os模块
    我的github代码添加
    Python正则表达式+自创口诀
    自己总结python用xlrdxlwt读写excel
    CentOS安装+配置+远程
  • 原文地址:https://www.cnblogs.com/liuwanpeng/p/6652327.html
Copyright © 2011-2022 走看看