线程概念与线程控制

目录

1.线程概念

2.线程控制


1.线程概念

  • 1.1线程与进程的关系
    • 线程是依附于进程才能存在的,如果没有进程,则线程不会单独存在
    • 多线程其实是为了提高整个程序的运行效率
    • 线程是执行流,执行用户写的代码
    • 在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是"一个进程内部的控制序列"
  • 1.2曾经写的代码当中有没有线程呢?
    • 理解以前的:
      • 进程在内核当中就是一个task_struct,在该结构体当中的成员变量 pid 被我们称之为进程号。
    • 现在要理解的:
      • 1.linux操作系统当中没有线程的概念,程序员说的创建线程,本质上在1inux操作系统当中创建轻量级进程(1wp),所以轻量级进程等价于线程。
    • 答案是:有的!曾经写的代码当中存在线程,就是执行mian函数的执行流,被称之为主线程,而程序员创建的线程被称之为叫做“工作线程”。
    • 2. pid本质上是轻量级进程id,换句话说,就是线程ID
      • 在task_struct当中
        • pid_t pid; (轻量级进程id,也被称之为线程id),不同的线程拥有不同的pid
        • pid_t tgid;(轻量级进程组id,也被称之为进程id)
      • 一个进程当中的线程拥有相同的tgid
      • 为什么进程概念的时候,说pid就是进程id?
        • 因为主线程的pid和tgid相等
  • 1.3 linux内核创建线程
    • 即在当前轻量级进程组中加了一个task_struct结构体
  • 1.4 线程的共享与独有
    • 共享
      • 文件描述符表(线程A创建了一个文件,就会在文件描述表(fd_array数组)中添加一个新的元素,下标就是文件描述符,线程B也知道有这个文件)
      • 用户id,
      • 用户组id,
      • 信号处理方式(信号的处理方式是操作系统内核定义的,因此每个线程的信号处理方式都是一样的)
      • 当前进程的工作目录
    • 独有:在进程虚拟地址空间的共享区当中
      • 调用栈(线程调用函数时都会在自己的调用栈内开辟函数栈帧,就不会产生调用栈混乱的问题)
        • 主线程用的是进程虚拟空间当中栈,而工作线程用的是共享区当中自己独有的调用栈
      • 寄存器(线程退出的时候,要保存上下文信息)
      • 线程ID,
      • errno(线程执行自己的代码时,如果发生错误,就要设置自己的errno)
      • 信号屏蔽字(每个线程都有自己的信号的阻塞位图)
      • 调度优先级
    • vfork()函数,创建子进程,且父子进程指向的内存虚拟地址空间是相同的,为了避免调用栈混乱的问题,vfork()函数规定子进程不退出的情况下,父进程不能执行自己的代码,即将阻塞父进程
    • 1.5 线程的优缺点:
      • 优点:
        • 多线程的程序,拥有多个执行流,合理使用,可以提高程序的运行效率,多线程程序的线程切换比多进程程序快,付出的代价小(有些可以共享的数据(全局变量)就能在线程切换的时候,不进行切换
        • 可以充分发挥多核CPU并行的优势
          • 计算密集型的程序,可以进行拆分,让不同的线程执行计算不一样的事情
          • I/0密集型的程序,可以进行拆分,让不同的线程执行不同的I/O操作,可以不用串型运行,提高程序运行效率
      • 缺点:
        • 编写代码的难度更加高(需要加锁,添加条件变量控制进程)
        • 代码的(稳定性)鲁棒性要求更加高(保证资源分配的合理性)
        • 线程数量并不是越多越好(CPU数量是固定的,线程数量过多会导致线程切换耗时大于正常业务耗时,反而降低了程序运行效率)(滑稽吃鸡)
          • 程序的运行效率,是基于某个固定的机器配置,通过测试才能得出来
        • 缺乏访问控制,可能会导致程序产生二义性结果
        • 一个线程崩溃,会导致整个进程退出。(滑稽吃鸡)

2.线程控制

  • 2.1线程创建
    • 2.1.1接口
      • int pthread_create(pthread_t *thread, const pthread_ attr_t *attr , void *(*start_routine) (void*), void *arg);
        • thread :出参,获取线程标识符(地址),本质上就是线程独有空间的首地址
        • attr :线程的属性信息,一般填写NULL,采用默认的线程属性
          • 属性信息当中比较关心的:
          • 调用栈的大小(获取调用栈的大小是防止栈溢出的手段)
          • 分离属性
          • 调度策略:先来先服务,分时策略,时间片轮转,调度优先级等等
        • start_routine :函数指针,线程执行的入口函数(线程执行起来的时候,从该函数开始运行,切记:不是从main函数开始运行)
        • arg:给线程入口函数传递参数;
        • 返回值:
          • 成功,0
          • 失败,<0
      • 代码验证1:
      • pstack [进程号] 命令
        • 查看调用堆栈,从下往上看
      • top -H -p [进程号] 命令
        • -H:查看某个进程中线程的信息
        • -p:指定进程号
      • 代码验证2:
        • 结论:线程把自己的入口函数执行完就会退出
      • 代码验证3:创建多个线程
        • 结论1:不要传递临时变量给线程的入口函数
        • 结论2:如果给线程入口函数传递了一个从堆上开辟的空间,让线程自行释放
  • 2.2线程终止
    • void pthread_exit (void *retval );
      • retval :线程退出时,传递给等待线程的退出信息。
      • 作用:谁调用谁退出
    • int pthread_cancel (pthread_t thread);
      • 退出某个线程
      • thread:被终止的线程的标识符
    • pthread_t pthread_self(void);
      • 谁调用获取谁的线程标识符
      • 测试1:工作线程自己调用Pthread_cancel
      • 测试2:让主线程退出,工作线程不退出
      • 测试3:让主线程退出,工作线程执行一段时间后退出
      • 测试4:让主线程退出,且主线程退出语句之后不再有循环体,工作线程执行一段时间后退出
  • 2.3线程等待
    • 1.线程被创建出来的默认属性是joinable属性,退出的时候,依赖其他线程来回收资源(主要是退出线程使用到的共享区当中的空间)
    • int pthread_join(pthread_t thread,void **retval);
      • 是一个阻塞调用接口
      • thread:线程的标识符(等待哪个线程就把哪个线程的线程标识符传递给函数)
      • retval:退出线程的退出信息
        • 第一种:线程入口函数代码执行完毕,线程退出的,就是入口函数的返回值
        • 第二种:pthread_exit退出的,就是pthread_exit的参数
        • 第三种:pthread_cancel退出的,就是一个宏:PTHREAD_CANCELED(在这种情况下,由于当前线程可能是其他线程终止的,当前线程不知道为啥退出,因此退出信息只能是一个宏)
    • 代码验证:
  • 2.4线程分离
    • 设置线程的分离属性,一旦线程设置了分离属性,则线程退出的时候,不需要任何人回收资源。操作系统可以进行回收
    • int pthread_detach(pthread_t thread) ;
      • thread:设置线程分离的线程的标识符

猜你喜欢

转载自blog.csdn.net/sy2453/article/details/124274459
今日推荐