zoukankan      html  css  js  c++  java
  • 【linux】系统编程-5-线程


    前言

    7. 线程

    7.1 概念

    • 进程:进程是资源管理的最小单位

    • 线程:线程是程序执行的最小单位

    • 因为进程开销大,才衍生出线程

      • 进程切换上下文时, 需要重新映射虚拟地址空间、进出OS内核、寄存器切换,还会干扰处理器的缓存机制
    • 一个进程至少需要一个线程作为它的指令执行体,进程管理着资源(比如cpu、内存、文件等等), 而将线程分配到某个cpu上执行

    • 新的执行线程将拥有自己的栈,但与它的创建者共享全局变量、文件描述符、信号处理函数和当前目录状态

    • 特点:

      • 一个程序至少有一个进程,一个进程至少有一个线程
      • 线程使用进程的资源,进程崩溃,线程也随之崩溃
      • 线程切换上下文快,进程切换上下文慢
    • 使用 pthread_create 创建线程

    • 使用 int pthread_attr_destroy(pthread_attr_t *attr); 函数来销毁一个线程属性对象

    • 使用 pthread_attr_init() 函数可以初始化线程对象的属性

    • 使用 pthread_join() 等待线程结束

    • 使用 pthread_detach() 来设置线程为分离状态

    7.2 创建线程

    7.2.1 pthread_create()

    • 使用 pthread_create 创建线程
    • 通过命令 man 了解更多
    • 所需要的头文件:
      #include <pthread.h>
      
    • 函数原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
      • thread:指向线程标识符的指针
      • attr:设置线程属性
      • start_routine:函数指针,线程入口
      • arg:传给线程入口函数的参数

    7.3 设置线程属性

    • 线程属性结构体 pthread_attr_t
      typedef struct{
          int                   etachstate;      //线程的分离状态
          int                   schedpolicy;     //线程调度策略
          structsched_param     schedparam;      //线程的调度参数
          int                   inheritsched;    //线程的继承性
          int                   scope;           //线程的作用域
          size_t                guardsize;       //线程栈末尾的警戒缓冲区大小
          int                   stackaddr_set;   //线程的栈设置
          void*                 stackaddr;       //线程栈的位置
          size_t                stacksize;       //线程栈的大小
      }pthread_attr_t;
      
    • 注意:
      • 因为 pthread 并非 Linux 系统的默认库,而是 POSIX线程库。在Linux中将其作为一个库来使用, 因此编译时需要加上-lpthread(或-pthread)以显式指定链接该库
      • 函数在执行错误时,并不把错误信息写到变量 error 中,而是作为返回值返回
    • 线程属性不能直接设置,只能通过函数操作
    • 线程属性包括:作用域(scope)、栈大小(stacksize)、栈地址(stackaddress)、优先级(priority)、 分离的状态(detachedstate)、调度策略和参数(scheduling policy and parameters)
    • 线程的默认属性:非绑定、非分离、1M的堆栈大小、与父进程同样级别的优先级
    • API:
    API 说明
    pthread_attr_init() 初始化一个线程对象的属性
    pthread_attr_destroy() 销毁一个线程属性对象
    pthread_attr_getaffinity_np() 获取线程间的CPU亲缘性
    pthread_attr_setaffinity_np() 设置线程的CPU亲缘性
    pthread_attr_getdetachstate() 获取线程分离状态属性
    pthread_attr_setdetachstate() 修改线程分离状态属性
    pthread_attr_getguardsize() 获取线程的栈保护区大小
    pthread_attr_setguardsize() 设置线程的栈保护区大小
    pthread_attr_getscope() 获取线程的作用域
    pthread_attr_setscope() 设置线程的作用域
    pthread_attr_getstack() 获取线程的堆栈信息(栈地址和栈大小)
    pthread_attr_setstack() 设置线程堆栈区
    pthread_attr_getstacksize() 获取线程堆栈大小
    pthread_attr_setstacksize() 设置线程堆栈大小
    pthread_attr_getschedpolicy() 获取线程的调度策略
    pthread_attr_setschedpolicy() 设置线程的调度策略
    pthread_attr_setschedparam() 获取线程的调度优先级
    pthread_attr_getschedparam() 设置线程的调度优先级
    pthread_attr_getinheritsched() 获取线程是否继承调度属性
    pthread_attr_getinheritsched() 设置线程是否继承调度属性

    7.3.1 pthread_attr_init()

    • 使用 pthread_attr_init 初始化线程对象属性
    • 通过命令 man 了解更多
    • 所需要的头文件:
      #include <pthread.h>
      
    • 函数原型:int pthread_attr_init(pthread_attr_t *attr);
      • attr:指向一个线程属性的指针
      • 返回:
        • 成功:返回 0
        • 失败:返回 非 0

    7.3.2 销毁一个线程属性对象

    • 使用 int pthread_attr_destroy(pthread_attr_t *attr); 函数来销毁一个线程属性对象
      • attr:指向一个线程属性的指针
      • 返回:
        • 成功:返回 0
        • 失败:返回 错误码

    7.3.3 线程的分离状态

    • 在任何一个时间点上,线程是可结合的(joinable),或者是分离的(detached)

      • 可结合的线程:能够被其他线程收回其资源和杀死;在被其他线程回收之前,它的存储器资源(如栈)是不释放的
      • 分离的线程:不能被其他线程回收或杀死的,它的存储器资源在它终止时由系统自动释放
    • 线程的分离状态决定一个线程以什么样的方式来终止自己

    • 默认状态下是 非分离状态

    • 函数:

      • int pthread_join(pthread_t tid, void **rval_ptr);
        • 等待某个线程的终止,获得该线程的终止状态,并收回所占的资源
          • tid:线程标识符
        • rval_ptr设置为NULL,则忽略返回状态
      • int pthread_detach(pthread_t tid);
        • 将线程设置为分离状态
        • 头文件:#include <pthread.h>
        • tid:线程标识符
        • 返回:
          • 成功:返回 0
          • 失败:返回一个错误值:
            • EINVAL:tid 对应的线程不是一个 非分离的线程
            • ESRCH:找不到对应的线程
    • 非分离状态下:

      • 原有线程等待创建的线程结束,只有当 pthread_join() 函数放回时,创建的线程才算真正终止
    • 分离状态下:

      • 该线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源
    • 如果在创建线程时就知道不需要了解线程的终止状态,则可以 pthread_attr_t 结构中的 detachstate 线程属性,让线程以分离状态启动

      • 使用 pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate) 函数来设置线程分离状态
        • attr:指向一个线程属性的指针
        • detachstate
          • PTHREAD_CREATE_DETACHED:分离线程
          • PTHREAD _CREATE_JOINABLE:非分离线程
    • 获取某个线程的分离状态

      • 使用 int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate); 函数获取线程的分离状态
      • attr:指向一个线程属性的指针
      • detachstate:保存状态的值
      • 返回:
        • 成功:返回 0
        • 失败:返回 错误码
    • 注意:

      • 如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在 pthread_create 函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用 pthread_create 的线程就得到了错误的线程号。要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用 pthread_cond_timewait 函数,让这个线程等待一会儿,留出足够的时间让函数 pthread_create 返回。设置一段等待时间,是在多线程编程里常用的方法。但是注意不要使用诸如 wait() 之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。

    7.3.4 线程的调度策略

    • POSIX 标准指定了三种调度策略:

      1. 普通线程(默认)SCHED_OTHER。采用时分调度策略,不需要实时机制。另外两种用于超级用户权限时运行。
      2. 实时线程SCHDE_FIFO。采用实时调度-先进先出方式策略。一旦占用cpu则一直运行,直到有更高优先级任务到达或自己放弃。
      3. 轮询调度SCHED_RR。采用实时调度-时间片轮转方式调度策略。当任务的时间片用完,系统将重新分配时间片,并置于就绪队列尾。放在队列尾保证了所有具有相同优先级的RR任务的调度公平。所以说SCHED_RR=SCHED_OTHER+SCHDE_FIFO。
    • 与调度相关的API

      • 参数说明:
        • attr:指向一个线程属性的指针。
        • inheritsched:线程是否继承调度属性,可选值分别为
          • PTHREAD_INHERIT_SCHED:调度属性将继承于创建的线程,attr中设置的调度属性将被忽略。
          • PTHREAD_EXPLICIT_SCHED:调度属性将被设置为attr中指定的属性值。
        • policy:可选值为线程的三种调度策略
          • SCHED_OTHER
          • SCHED_FIFO
          • SCHED_RR
      int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched);
      int pthread_attr_getinheritsched(const pthread_attr_t *attr, int *inheritsched);
      
      int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
      int pthread_attr_getschedpolicy(const pthread_attr_t *attr, int *policy);
      

    7.3.5 线程优先级

    • SCHED_OTHER调度时
      • 静态优先级必须置为 0
      • 处于静态优先级为 0 的线程按照动态优先级被调度
      • 动态优先级值起始于 nice 值,且当前线程处于就绪态并被调度器无视时,其动态值++,以保证竞争CPU的公平性
    • 线程优先级的设置(静态优先级)(SCHED_FIFO和SCHED_RR)
      • 通过以下函数来获得线程可以设置的最高和最低优先级(不支持SCHED_OTHER)
      int sched_get_priority_max(int policy);
      int sched_get_priority_min(int policy);
      
      • 通过以下两个函数来设置和获取优先级
      int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param);
      int pthread_attr_getschedparam(const pthread_attr_t *attr, struct sched_param *param);
      
    • 线程优先级特点
      • 新线程的优先级默认为 0
      • 新线程不继承父线程调度优先级(PTHREAD_EXPLICIT_SCHED)
      • 当线程的调度策略为SCHED_OTHER时,不允许修改线程优先级,仅当调度策略为实时(即SCHED_FIFO或SCHED_RR)时才有效, 并可以在运行时通过pthread_setschedparam()函数来改变,默认为0。

    7.3.6 线程栈

    • 线程栈:
      • 用于存放函数形参、局部变量、线程切换现场寄存器等数据。
      • 使用的是进程的地址空间,默认线程栈大小是 1M
      • 设置和获取线程大小可以使用一下函数
        int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
        int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize);
        

    7.4 退出线程

    • 线程退出使用 void pthread_exit(void *retval); 函数
    • 注意:不能使用 exit() 函数退出,因为该函数是直接退出进程的,会把进程内所有函数都退出。

    参考

      * 野火
  • 相关阅读:
    LeetCode 345. Reverse Vowels of a String 题解
    LeetCode 344. Reverse String 题解
    LeetCode 27. Remove Element 题解
    LeetCode 61. Rotate List 题解
    LeetCode 19.Remove Nth Node From End of List 题解
    Android耗电量
    Android 使用adb查看和修改电池信息
    Android AOP AspectJ 插桩
    Flask相关用法
    Monkey日志信息的11种Event percentage
  • 原文地址:https://www.cnblogs.com/lizhuming/p/14231490.html
Copyright © 2011-2022 走看看