线程原理, 线程控制(线程创建, 线程终止, 线程等待, 线程分离)

什么是线程???

  • 在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的控制序列”
  • 一切进程至少都有一个执行线程
  • 线程在进程内部运行,本质是在进程地址空间内运行;
  • 在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化
  • 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流

线程的优点

  • 创建一个新线程的代价要比创建一个新进程小得多
  • 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
  • 线程占用的资源要比进程少很多
  • 能充分利用多处理器的可并行数量
  • 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
  • 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
  • I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
    1. 线程的原理(内核角度分析)
        1.1 线程也是内核创建出来的task_struct结构体, 换句话说, 也是在内核中创建了一个PCB
        1.2 从内核角度看, 内核当中其实是没有线程概念的, 叫做轻量级进程
        1.3 内核创建出来的轻量级进程(线程)的PCB, 在task_struct结构体当中内存指针也是指向进程的虚拟地址空间
            pid_t pid: 轻量级进程id --> 线程id(执行流的id)
            pid_t tgid: 线程组id, 进程id
            当执行流当中只有一个执行流时, 也就是只有main函数的执行流时 pid = tgid (这个娃(pid)是这个家(tgid))
            进程的进程号 = tgid 
            执行main函数的现场我们称之为主线程
                主线程的pid一定等于tgid
            创建出来的线程我们称之为工作线程
                工作线程的pid不等于tgid
                但是数据线程组当中的线程, 

            对于轻量级进程的解释:   
                对内核而言, 是没有线程概念的, 线程的概念是C库当中的
                pthread_create... 这些接口都是库函数
        fork/vfork/pthread_create在内核中都调的是克隆接口(clone), 他们的区分就在clone接口的参数当中

        线程的优点: (桌子上有若干鸡腿,桌子旁边坐了很多人, 吃鸡, 创建线程(), 创建进程(人+桌子+鸡腿))
            创建一个线程的开销要比创建一个进程小(只需要创建一个执行流去执行我们之前的代码即可)
            创建一个线程所用的资源比较小
            进程当中的多个线程可以并行的运行
            多线程程序可以提高程序运行效率
        线程的缺点:
            健壮性低,(当前这个程序容错能力低), 若并行运行,打架,把桌子掀了, 鸡腿所有的线程都吃不了了, 所以要注意所属性
            多线程程序当中有一个执行流异常的情况下, 会导致整个进程异常, 缺乏访问控制
            编程难度高
            性能损失: 当线程切换时, 有可能切换的成本比处理业务的成本还高, 导致程序在切换线程的时候占用时间比较多, 所以线程不是说越多就越好(桌子没有座位了, 拉扯 让凳子的过程浪费时间 耗性能).
    	线程异常
			单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃
			线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出
		线程用途
			合理的使用多线程,能提高CPU密集型程序的执行效率
			合理的使用多线程,能提高IO密集型程序的用户体验(如生活中我们一边写代码一边下载开发工具,就是多线程运行的一种表现)
			
        线程的独有和共享
        进程是资源分配的基本单位 ; 线程是调度的基本单位 ; 线程共享进程数据,但也拥有自己的一部分数据
            独有:
                线程ID: tid
                栈:
                信号屏蔽字: 不同的线程对于信号的屏蔽字
                调度优先级
                errno:每一个执行流都有自己独有的errno
                一组寄存器:
            共享
                进程虚拟地址空间
                文件描述符表
                当前进程工作路径
                用户ID和用户组ID

    2. 线程控制
      接口: 都是库函数, 在使用这些接口时 ,需要链接pthread库, 链接的时候增加-lpthread
        2.1 线程创建
            int pthread_create(pthread_t* thread, const pthread_attr_t* attr, void*(start_routine)(void*), void* arg);
                thread: 线程的标识符, 不等同于线程ID, 是一个出参(用指针作为参数传出参数; 入参的值是被调函数需要, 出参的值是主调函数需要的), 本质上是线程独有空间的首地址(空间的首地址就是线程的标识符)
                attr: 线程的属性, 在创建关键线程的时候, 如果为NULL, 则采用默认属性
                    pthread_attr_t类型 也是一个结构体, 里面包括线程栈的大小, 线程栈的起始位置, 线程的分离属性
         !!!!   start_routine: 是一个函数指针, 线程入口函数, 保存线程入口函数的地址 执行流就是从这里这个函数开始执行的, 而程序则是从main函数开始执行的
                arg: 给线程入口函数传递的参数
        注意:
           不能传递临时变量!!!!!!!!!!!!!!!!!!!!
            而是传递堆上开辟的内存, void*可以传递任意类型, 包含自定义数据结构, 包含类的实例化指针
            在线程入口函数内部, 进行强转, 强转完成之后使用
            但是 在堆上开辟内存, 一定要记得释放内存, 否则就会造成内存泄漏

        2.2 线程终止
            线程终止的方式: 
                1. 从线程入口return返回 类型为void*, 一般不去指定, 直接传递NULL
                2. pthread_exit(void* revtal) ---> 谁调用谁退出(自杀)
                    retval: 当前线程的退出信息, 也可以传递NULL值, 
             主线程调用pthread_exit之后 会有怎样的效果
                 当主线程调用pthread_exit退出后, 进程是不会退出的, 但是主线程的状态变成了Z,也就是线程变成了"僵尸线程", 而工作线程的状态还是R或者S(用这个top -H -p + pid号可以查看哪一个线程消耗内存), R(运行)S(睡眠,循环)
                3. pthread_cancel(pthread_t thread) -- 只要传递了线程的标识, 就可以结束任一一个线程
                这个函数的权限更大, 可以去杀别人, 但是exit函数只可以杀自己
                    pthread_self() ---> 这个接口可以获取自己线程的线程标识;
                    可在main函数中调用cancel函数, 跑一遍就没了, 

        2.3 线程等待
            1. 原因: 线程在采用默认属性进行创建的时候, 线程的属性是joinable的, 当线程退出的时候 如果为joinable属性, 则需要其他线程来回收退出线程的资源, 否则在共享区当中退出线程的空间还在保留, 且保留的空间不能被复用, 这样就会造成内存泄漏
            2. 接口:
                pthread_join(pthread_t, void**);
                    pthread: 等待的哪一个线程(当工作线程全部退出后, 主线程就不会再等待了)
                    void**: 获取退出信息的, 前面返回void*, 这里用void**接收, 
                        1. 线程入口函数退出, void**保存返回值
                        2. pthread_exit退出, void**保存pthread_exit的参数
                        3. pthread_cancel: void** 获取到一个常数, PTHREAD_CANCEL

        2.4 线程分离
            当我们设置线程的属性为detach属性后, 当线程退出时, 不需要其他线程来回收退出的资源, 其他的线程也就不需要再等待了
            int pthread_detach(pthread_t thread);
                pthread_t: 线程的标识, 分离哪一个线程, 只需要知道线程的标识符即可, 
                    也可在main函数中调用, 传入线程的tid[i], 达到分离其他线程的目的
                    在线程入口函数里调用时, 线程自己退出时就与主线程分离了, 不需要主线程再去等待
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值