多线程理解

目录

1、二级页表和数据类型

2、线程

(1)线程特点

(2)进程VS线程

(3)线程的创建

(4)已经有多进程,为什么还要有多线程

(5)面试题:为什么线程调度成本更低?

(6)对函数和线程的理解:

(7)线程私有:寄存器和栈

在正式开始多线程理解之前,还需要有一些相关理解,以便于后期更全面、深刻的理解多线程。

1、二级页表和数据类型

虚拟地址如何转化为物理地址?

下面我们以32位系统为例子介绍

物理地址是一个个的单位为4069B大小的数据块,称为页框
地址32位,前10位作为索引,共有1024项,称为页目录,内存放页表的地址
页表存后10位,也视为索引,共有1024项,页表存放页框起始地址
通过前面20位就可以找到对应物理内存的具体页框的起始位置
但是,还要再具体些,找到某一个具体的字节,怎么办?
在前面20位索引的基础上,再加上虚拟地址的最后12位,即2^12,共有[0,4095]
加上最后的12位,就是在页框起始位置的基础上再加上偏移量,就可以找到物理内存的任意位置
所以,通过前10位的虚拟地址,找对页目录
再通过后10位的虚拟地址,找到页框起始位置
再加上虚拟地址最后12位的偏移量,找到页框的具体位置
因此,页表的作用,其实是搜索页框
上述这种映射叫做二级页表
而64位系统,原理和上述一样,只是页表大小和多少的问题

但是,你会发现,按照上述的方法,其实只能找到一个字节
那么,我们怎么拿到多个字节?
例如,一个int就是4个字节,怎么拿?
这就是为什么编程语言要有数据类型的原因,让用户可以随机获取想要的数据大小
而数据类型的本质,就是告诉CPU取多少数据;而取多少数据的本质,是地址映射的范围!

那么页表存在那里?
存在cr寄存器中,这个cr寄存器直接集成在CPU内部。(关于CR寄存器,可以自行AI搜索了解)

2、线程

线程概念:在进程内部运行的轻量级进程,用于实现并发和并行处理(在地址空间中运行,)

进程的定义(内核观点):承担分配系统资源的基本实体

(1)线程特点

线程是进程中的一个执行单元,是CPU执行的最小单位。
每个线程都拥有自己的程序计数器、栈和局部变量,但与同一进程中的其他线程共享进程的资源。
在Linux中的线程,被视为轻量级的进程,即LWP,light weight process,因此并不占有单独的进程id,而是和进程共用;而在windows中是一个进程链表
共享资源:同一进程中的所有线程共享进程的资源,包括内存空间、文件描述符等。
独立执行:线程有自己的执行栈和程序计数器,能够独立执行代码。
并发性:多个线程可以在同一时间段内并发执行,提高程序的响应速度和处理能力。

(2)进程VS线程

进程:是操作系统分配资源的基本单位,包含了一个或多个线程。每个进程有自己独立的内存空间。
线程:是进程内的一个执行路径,与同一进程中的其他线程共享进程资源(如内存)。线程的开销较小,比进程创建和切换更为高效。
但是线程也有致命缺点:一个进程出问题,整个线程就出错了。
因为进程和线程共享数据,当任意一个线程出现问题,就会导致整个进程被操作系统发送对应信号处理,释放内存,杀掉;进而,所有相关线程也受影响。

(3)线程的创建

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine)(void *), void *arg);

参数说明

  • pthread_t *thread:

    • 描述: 线程标识符的指针,用于存储新创建线程的 ID。
    • 类型pthread_t 类型的指针。
  • const pthread_attr_t *attr:

    • 描述: 线程属性对象的指针,定义了线程的属性(例如栈大小、调度策略等)。如果传递 NULL,则使用默认属性。
    • 类型pthread_attr_t 类型的指针。
  • void *(*start_routine)(void *):

    • 描述: 线程的起始例程(线程函数),它的返回类型是 void*,并且接收一个 void* 类型的参数。
    • 类型: 函数指针,指向线程函数。
  • void *arg:

    • 描述: 传递给线程函数的参数,类型为 void*,可以传递任何类型的数据。
    • 类型void* 类型的指针。

返回值

  • 成功: 返回 0。
  • 失败: 返回错误码,
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <unistd.h>

// 线程函数
void *thread_function(void *arg)
{
    while (true)
    {
        sleep(1);
        std::cout << "new thread here " << std::endl;
    }
}

int main()
{
    pthread_t thread;
    int thread_arg = 1;

    // 创建线程
    pthread_create(&thread, NULL, thread_function, &thread_arg);
      while (true)
    {
        sleep(1);
        std::cout << "main thread here " << std::endl;
    }

    return 0;
}

线程创建完毕,主线程会向下执行
进程id和LWP一样的线程叫做主线程;新创建的线程不是父子关系,而是新旧关系

(4)已有多进程,为什么还要有多线程

因为进程创建、管理、调度成本非常高,而创建、管理、调度线程成本低
需要创建PCB、页表、内核空间、加载代码和数据、加载对应管理算法等等
而创建线程只需要创建一个PCB,把主线程的地址空间资源分配给他即可

(5)面试题:为什么线程调度成本更低?

因为cache的局部性原理
例如,当一个进程在运行时,CPU的cache内部会缓存相关数据
如果此时要更换一个进程,当前cache存储的数据是上一个进程相关的
和即将更换的进程无关,因此,当调度一个新的进程,就必须要到内存中加载相关数据
这就设计硬件IO的交互,相对很慢
但是如果是线程的调度,线程是在进程内部运行的
除了进程PCB外,其他数据都是和进程共享的
所以,当切换线程时,无需更换cache中的数据,而是直接复用
这就是导致线程调度和进程调度成本不同的根本原因。

(6)对函数和线程的理解:

函数是被统一编址的
函数被编址时,每一句代码都有地址,整个函数的本质就是一系列连续地址的组合,即代码块。
线程执行一个函数的本质,就是线程拥有这函数地址的代码块
这些地址是虚拟地址,也就是线程拥有一部分虚拟地址资源
而虚拟地址对应的是一部分的页表地址
因此,一个线程的本质,就是拥有页表的一部分
但是线程并没有自己的页表,都是共享的,只是线程执行一部分页表内容

(7)线程私有:寄存器和栈

线程不部分数据是拥有的,但是也有私有的部分:一组寄存器+栈
为什么?因为:
寄存器:存储硬件上下文数据---线程可以动态运行
栈:线程在运行的时候,会形成很多临时变量,这些临变量会被保存在自己所属的栈区中
这样,多线程调度时才不会冲突

原生线程库---pthread,对轻量级接口封装,按照线程的接口方式,交给用户

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

二十5画生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值