Linux-线程

目录

1. 线程概念

2. 线程vs进程

3. 线程的优缺点

4. 线程创建

4.1 pthread_create

4.2 pthread_self

5. 线程终止

5.1 return

5.2 pthread_exit

5.3 pthread_cancel

6. 线程等待

7. 线程分离


1. 线程概念

        线程:轻量级进程,在进程内部执行,是OS调度的基本单位;进程内部线程共用同一个地址空间,同一个页表,以及内存中的代码和数据,这些资源对于线程来说都是共享的资源 

        进程:在用户视角下,内核数据结构+该进程对应的代码和数据;在内核的视角下,承担分配系统资源的基本实体

        其实在之前没有了解线程之前的进程,就是一个只有一个主线程的进程

        现在又该如何理解线程和进程之间的关系呢?

         线程和进程之间就像家庭成员和家庭之间的概念,家庭是由所有家庭成员共同组成的,在这个社会中可能有三口之家,五口之家

        我们在社会中被描述为一个整体,比如户口本就是按家庭发放的,房屋是按家庭建造的,社会资源按家庭分配的,例如疫情期间买菜

        家庭成员和家庭之间的关系就是一荣俱荣,一损俱损的关系(线程异常时会触发信号机制,终止进程,内核所有线程都会退出

        我们在家庭中扮演不同的角色,也在使用不同的资源,家庭负责分配这些资源,其中有大部分资源是家庭成员之间共享的,比如卫生间,客厅,但总有一个房间是给你住的,作为你的私人空间

        家庭资源:空间,数据

        私人空间:后面会说

         在Linux中,没有专门为线程管理,创建新的数据结构,而是沿用了进程的task_struct,用它来模拟线程,线程和进程有很多共性,为此Linux下的所有版本都安装了pthread系统库,为了提供用户使用的接口

        库底层调用了clone系统接口,所以系统提供一部分,库提供一部分,就组成了Linux下的线程

        所以使用线程库的时候,需要包含头文件<pthread.h>,编译时需要-lpthread

        线程共享进程数据,但也拥有自己的一部分数据

线程独有:

  • 线程ID
  • 一组寄存器(上下文)
  • 独立栈结构
  • errno变量在线程内部
  • 信号屏蔽字(block)
  • 调度优先级

线程共有:

  • 文件描述符表
  • 信号处理方式
  • 当前工作目录
  • 用户id和组id(权限)

        线程独立的栈结构(保存临时数据)和上下文数据,体现了线程的动态属性

线程如何实现独立的栈结构:

        线程库提供了线程创建接口,它会在地址空间的共享区中创建对应线程的栈结构,其实在上面系统接口中就有所体现clone中有一个参数child_stack的指针,栈地址,栈的组成应该是系统提供的

        通过clone系统接口的第一个参数函数指针,其实如何让线程执行自己代码,其实就是执行函数代码

2. 线程vs进程

        进程是资源分配的基本单位

        线程是CPU调度的基本单位

        线程切换的成本更低

        因为线程间共享地址空间和页表,这些不需要切换

        其次,CPU内部有L1~L3 的 cache 高速缓存(对内存的代码和数据根据局部性原理,预读到CPU内部,进而提高效率)也不用切换

3. 线程的优缺点

优点:

  • 创建,切换,占用资源都比进程小
  • 能充分利用多处理器可并行的数量
  • 在等待慢速IO操作结束的同时,程序可以执行其他任务
  • 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中
  • IO密集型应用,为了提高性能,将IO操作重叠,可以同时等待不同的IO

缺点:

        缺乏访问控制(后续会学互斥,同步,信号量来解决这一问题)


4. 线程创建

        这里介绍pthread线程库的接口,他们都有一个规律,函数都是以pthread_为开头,且函数的返回值都是成功返回0,失败返回errno,因为每一个线程都有独立的errno

4.1 pthread_create

函数功能:
        创建一个线程

参数:

        thread:输出型参数,返回线程ID(NPTL后面解释)

        attr:设置线程属性,一般为nullptr

        start_routine: 线程执行的函数地址,函数类型必须参数和返回值都是void*

        arg:作为传给线程函数的参数

        线程ID有两个,一个属于OS系统层面的,一个属于NPTL(Nativa POSIX Thread Libaray本地可移植性操作系统接口线程库)本地线程库层面的

        在Linux内核中,OS其实在调度的时候并不知道是线程还是进程,它只认识task_struct

struct task_atruct
{
    pid_t pid;
    pid_t tgid; // Thread Group ID
    struct task_struct *group_leadler;
    struct list_head thread_group;
    // ...
}

        在操作系统看来它最关心的是pid作为线程的唯一标识符,而tgid则是线程组的id,也就是进程id。在用户层调用getpid()不论是在哪个线程内部,返回的都是进程的id,所以其实getpid()底层获取的是task_struct的tgid。

        用户可以通过指令查看当前的进程id

ps -eLf

        其中选项-L会显示LWP:线程id 和 NLWP:线程组内线程个数

        在线程组内,所有的线程都是对等关系

        pthread_t 类型其实是线程库里定义的线程id,其实就是线程结构体在地址空间的起始地址,线程库的线程id用于线程库接口的使用

4.2 pthread_self

 函数功能:

        获得自己的线程ID,返回值就是


5. 线程终止

5.1 return

        return返回值可以设为nullptr标识什么都不返回,也可以返回指针指向的全局静态或者动态分配的空间,但是不能返回栈上的临时数据,因为线程终止时,线程的栈会被销毁

5.2 pthread_exit

函数功能:

        终止自己这个线程

参数:

        retval:和return返回的值一个道理

5.3 pthread_cancel

函数功能:

        终止一个正在执行的线程,一般用于终止其他线程

参数:

        thread:要被终止的线程id

注意:

        pthread_cancel(pthread_self());不推荐这样做,因为这样操作是未定义的,会出现奇怪的错误


6. 线程等待

        已经退出的线程如果不等待回收,其空间没有被释放,会处于“僵尸”状态,造成内存泄漏

函数功能:
        等待回收线程

参数:

        thread:被等待的线程id

        retval:输出型参数,用于获取线程终止时的返回值,不关心设置nullptr,因为线程的返回值是void*,所以在函数内部拿到这个值,就要void**

        当return/pthread_exit终止时,拿到线程函数的返回值

        当pthread_cancel异常终止时,拿到PTHREAD_CANCELED(-1)


7. 线程分离

        一般创建的线程都需要pthread_join等待,但若不关心线程的返回值,join就是一种负担,分离线程,线程退出时,自动释放资源

函数功能:

        线程分离

参数:

        thread:线程id

注意:

        1. pthread_detach(pthread_self());这里可以使用

        2. joinable和分离是冲突的,一个线程不能同时被等待,又被分离

完。

  • 31
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值