【Linux】线程概念|线程优缺点|LWP|轻量级进程|cache

目录

一,线程的基本概念

二,如何重新看待进程

线程的优点

线程的缺点

线程异常

线程和进程

三,OS和用户对于"线程"的认识区别 

三,创建线程 

查看轻量级进程

证明第四个参数是传入给第三个参数 

四,进程VS线程 

cache

 五,线程的缺点 

理解LWP与TID


一,线程的基本概念

  1. 线程指的是进程中一个单一顺序的控制流(执行流),属于进程的一部分
  2. 一个进程中可以并发多个线程,每条线程并行执行不同的任务(进程:线程 = 1:n)
  3. 线程是独立调度和分派的基本单位,而进程是分配资源的基本单位
  4. 同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的pcb,调用栈,自己的寄存器环境(上下文),自己的线程本地存储

总结:

线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位,比进程更细更轻量化(其实CPU调度的都是轻量化进程)。线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源(自己的调用栈,寄存器环境等),但线程可以共享进程所拥有的全部资源,同时线程也拥有各自私有资源(不能被其他线程共享)

创建 task_struct,且创建出来的 task_struct 和原来的 task_struct 共享进程地址空间和页表,事实上,我们所创建的其实就是三个线程,只不过这三个线程共用一张 mm_struct 和 一张页表,这三个 task_struct 就是三个不同的执行流;

总结:

在Linux中创建线程,只需创建相应的 PCB 即可,所以在Linux中,线程是在进程的内部 “运行”,线程在该进程的地址空间内 “运行”,拥有该进程的一部分资源

二,如何重新看待进程

进程 = 内核数据结构 + 进程对应的代码和数据,现在要以全新的视角看待进程:内核视角

以内核的视角看待进程:进程是承担分配系统资源的基本实体

Linux进程 = 大量的task_struct + 一个虚拟地址空间 + 页表 + 一部分的物理内存;
我们之前篇章所谈的进程 = 一个task_struct + 一个虚拟地址空间 + 页表 + 一部分的物理内存
一个进程的创建:必定要花费相应的资源;

之前所谈的进程也是承担分配系统资源的基本实体,只不过,该进程的内部只有一个执行流(一个 task_struct),而现在所谈的进程也是承担分配系统资源的基本实体,只不过,该进程内部有多个执行流(多个 task_struct),进程的内部允许只有自己一个执行流,也可以允许有多个执行流,我们之前介绍的进程,内部只有一个执行流,以前所讲的 “进程” 只是一个子集,今天所讲的进程才是全貌;

线程的优点

  • 共享资源:同一进程内的线程共享地址空间,能访问相同的全局变量,堆和文件描述符等。这种共享使得线程间通信变得更加高效。
  • 独立的执行流:每个线程都有自己的程序计数器、寄存器和栈,这使得线程可以独立执行。线程的独立性使得多个线程并行执行多个任务,提高了此程序的响应性和吞吐量。
  • 轻量级:相比于进程,线程的创建开销会小很多,且不需要分配独立的地址空间。上下文切换也比进程快,因为不涉及地址空间的切换。
  • 并发执行:在多核处理器上,不同的线程可以做到真正的并行执行

线程的缺点

  • 同步复杂性:由于共享进程空间,多个线程同时访问和修改共享数据时可能会导致数据不一致等问题
  • 性能损失:使用锁和其它同步机制会导致性能下降
  • 调试难度提高:编写和调试一个多线程程序要比单线程程序困难得多

线程异常

  • 单个线程如果出现除0或者访问野指针等问题导致线程崩溃,进程也会随着崩溃
  • 进程终止,该进程的所有线程都会终止。这也就意味着,如果某一个线程出了异常进而终止进程的话,其它的线程也都会被终止。这也是线程不安全的原因之一。

线程和进程

进程是资源分配的基本单位,而线程是调度的基本单位。
线程共享以下进程资源:

  • 代码段和数据段
  • 文件描述符表
  • 每种信号的处理方式即handler表
  • 环境变量包括当前工作目录
  • 用户id和组id

虽然线程共享进程的数据,但有属于自己的一些数据:

  • 线程ID
  • 一组寄存器:(硬件上下文的数据,线程切换时 寄存器会保存上下文数据-动态运行)
  • 栈:线程运行时,会形成各种临时变量,临时变量会被保存在各自线程的栈区
  • errno错误流
  • 信号屏蔽字
  • 调度优先级

三,OS和用户对于"线程"的认识区别 

OS 只认线程,用户也只认线程,没有轻量级进程的概念,所以Linux无法直接提供线程操作的系统调用接口,而只能给我们提供轻量级进程的接口。

但是用户只认线程,所以Linux给我们提供了一个线程库,这个库是用户级线程库,它底层是调用轻量级进程的接口的,这个线程库对这些接口进行封装,上层用户使用这个库看起来像是Linux拥有线程一样
这个线程库的名字叫 pthread,是用户级线程库
任何的Linux系统,必须要提供这个库,这个线程库是默认携带的;

这个线程库也称为原生线程库
所以用户需要关心这个线程库所提供的接口,不用关心底层的接口

三,创建线程 

 pthread_create - create a new thread(创建一个新线程)
 
 
头文件:#include <pthread.h>
 
函数原型
 int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);
 
参数
    第一个参数thread,代表线程ID,是一个输出型参数,pthread_t是一个无符号整数
    第二次参数attr,用于设置创建线程的属性,传入空表示使用默认属性
    第三个参数start_routine,是一个函数的地址,该参数表示新线程启动后要跳转执行的代码
    第四个参数arg,是start_routine函数的参数,用于传入
 
返回值 
成功返回0,失败返回错误码

 第三个参数说明:void *(*start_routine) (void *)

  • 该参数是一个函数指针,用于设置一个回调函数start_routine
  • 该函数的返回值是 void*,
  • 函数参数是 void*,该参数由第四个参数 arg 传入

记得添加库  

test:test.cpp
	g++ -o $@ $^ -std=c++11	-lpthread
.PHONY:clean
clean:
	rm -rf test
void* handler(void* arg)
{
    while(true)
    {
        cout<<"new thread..."<<endl;
        sleep(1);
    }
}
int main()
{

     //int pthread_create(pthread_t *thread, const pthread_attr_t *attr,  void *(*start_routine) (void *), void *arg);
    pthread_t pid;
   int n =  pthread_create(&pid,nullptr,handler,(void*)"thread one");
   if(n!=0) //创建失败
   {
    perror("pthread_create!\n");
    return 1;
   }
    while(true)
    {
        cout<<"main thread ..."<<endl;
        sleep(1);
    }
    return 0;
}

进行运行,发现主线程和新线程在同时运行 

ps axj | head -1 && ps axj | grep test | grep -v grep

查看轻量级进程

查看轻量级线程相关信息的命令:ps -aL 

LWP(Light Weight Process)就是所谓的轻量级进程 

  • 每个轻量级进程的PID都是一样的
  •  每个轻量级进程LWP的ID都是不一样的

所以,CPU在调度的时候,是以 LWP 的ID作为唯一的标识符用来标识一个执行流的,并不是使用PID  

这就是以前写的单执行流的代码,只有一个执行流的时候,PID和LWP是等价的;

注意:信号是整体给进程发送的,不能单独发给一个 LWP 

证明第四个参数是传入给第三个参数 

 pthread_create 函数的第一个参数是一个输出型参数,返回的是线程ID,pthread_t 是一个无符号整数。

下面进行验证,该参数输出的是

typedef unsigned long int pthread_t;

 这个id十六进制地址和LWP的id是什么关系,等线程控制详细介绍

四,进程VS线程 

进程和线程切换时,消耗是不一样的;

  •  进程切换:需要切换页表 && 切换虚拟地址空间 && 切换PCB && 切换上下文数据
  • 线程切换:需要切换PCB && 切换上下文数据
  • 线程切换cache 不用更新太多,但是进程切换需要全部更新cache(主要体现在这点)

cache

cache是集成在CPU里面的,是一个硬件,是CPU很重要的组成部分,它具有数据保存的功能,它的缓存速度比寄存器慢,比内存快

 寄存器读取数据是直接在 cache 里面读取的,不是直接从内存读取,一个进程只有运行一段时间后,cache 里面才会缓存大量的热点数据;

热点数据就是:进程经常使用、经常访问、经常命中的数据(需要进程跑一段时间才会存在大量的热点数据),热点数据是被整个进程共享的;;

  • 线程切换的时候,cache内缓存的数据不用切换(线程的数据共享相同的缓存),线程需要用到新的数据直接缓存进cache即可
  • 而进程切换,cache内缓存的数据需要全部切换,新切换的进程需要重新缓存数据,这样效率就比线程慢得多了 
  • 进程切换可能导致缓存中的数据失效,因为新进程可能不会使用旧进程缓存的数据。

 五,验证线程的缺点 

 最主要的一个缺点,也是它的优点导致,因为多个线程共享内存,这也导致了如果一个线程崩溃了,该进程内的所有线程都会崩溃,新线程也会影响主线程。因为这些线程都在一个进程当中,当进程出问题时,操作系统会杀掉当前进程,释放内存;

缺乏健壮性;

注意:信号是叫做进程信号,是整体发给进程的 

理解LWP与TID

LWP本质是一个描述线程的一个概念,因为linux中没有单独为线程设计一套管理方案,又为了与进程PCB区分开(进程和线程实际上都被看作是task_struct的实例),就用LWP来表示线程。LWP和普通进程一样,由内核调度和管理。而TID是内核用来标识LWP的唯一标识符。

所有的 LWP 共享同一个进程的地址空间、打开的文件描述符、环境变量等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值