首页 > 其他分享 >Lec 10 线程

Lec 10 线程

时间:2025-01-11 13:22:04浏览次数:1  
标签:10 Lec 线程 内核 pthread 进程 exit NULL

Lec 10 线程

License

本内容版权归上海交通大学并行与分布式系统研究所所有
使用者可以将全部或部分本内容免费用于非商业用途
使用者在使用全部或部分本内容时请注明来源
资料来自上海交通大学并行与分布式系统研究所+材料名字
对于不遵守此声明或者其他违法使用本内容者,将依法保留追究权
本内容的发布采用 Creative Commons Attribution 4.0 License
完整文本

1 为什么需要线程

  • 进程的开销较大
    • 包括了数据,代码,堆栈等。
  • 进程的隔离性过强
    • 通过进程间通信(IPC),但是开销太大
  • 进程内部无法支持并行。

1.1 简单方法:进程+调度

  • 进程数量远远超过CPU的核数目
    • 简单分配,每个核都至少分到一个进程
  • 调度器分时复用,增加计算资源利用的效率
    • 通过调度策略,在进程需要等待的时候切换到其他进程执行。

img

  • 局限: 但一进程无法利用多核资源
    • 一个进程同一时刻只能被调度到其中一个核上运行
    • 如果一个程序想要同时利用多核怎么办?
    • Sol:采用fork()创建相似进程。创建的进程与原来的进程行为类似,可以用于其他核心的运行。

1.2 Fork 方法存在局限性

  • 进程间隔离过强,数据共享十分困难
    • 每个进程具有独立的虚拟地址空间,共享以页为粒度
    • 协调困难,需要复杂的通信机制。(pipe)
  • 进程管理开销大
    • 创建:地址空间的复制
    • 切换:页表切换

1.3 如何使得进程跨核心运行

  • Pros:无需使用fork创建新的进程
    • 降低进程管理的开销
    • 同一个地址空间数据共享和同步方便
  • 需要什么支持?
    • 处理器上下文:不同核心执行状态不同,需要独立处理器的上下文。

img

1.4 线程:更加轻量级的运行时抽象

  • 仅包含运行时状态

    • 静态部分通过进程提供
    • 包含了执行所需的最小状态
  • 一个进程可以包含多个线程

    • 每个线程共享同一个地址空间
    • 允许进程内并行

2 如何使用线程

  • 常用库:POSIX threads(pthreads)

    • 包含约60个函数的标准接口
    • 实现的功能与进程相关系统调用相似
      • 创建:pthread_create
      • 回收:pthread_join
      • 退出:pthread_exit
      • 暂停:pthread_yield
  • 注意:一个线程执行系统调用,可能影响该进程的所有线程

    • 如exit会使所有线程退出
/* 创建线程,打印"hello world!" */
#include <pthread.h>
#include <stdio.h>

/* 进程执行 */
// 子线程。
void *thread(void *args)
{
    // detach 分离线程
    pthread_detach(pthread_self());
    printf("Hello world!\n");
    return;
}

int main(int args, char *argv[])
{
    pthread_t tid;
    // 创建线程接口,赋予线程id,执行起点为thread函数
    // 属性通常为NULL,参数在第二个NULL传入。
    // 主线程。
    pthread_create(&tid, NULL, thread, NULL);
    // 解决线程提前结束的情况:等待对应的tid子线程结束后继续进行。
    pthread_join(tid, NULL); // 第二个参数接受返回值。
    exit(0);
}
  • 有时候没有输出!
  • 主线程创建子线程后,两线程独立执行
  • 若子线程先于exit执行,则printf顺利输出
  • exit会导致主线程和子线程全部终止。有可能会导致没有执行完成printf
  • 解决办法:加入join操作。

img

2.1 基于join的方法存在问题

  • 手动调用join回收资源,有可能导致资源溢出。(例如多次while循环创建进程导致报错:(stuck; errno:11)
  • 采用detach()操作:在线程函数内第一行增加pthread_detach(pthread_self());一行来完成分离。分离后的线程不会被其他线程杀死或回收,退出时资源自动回收。
  • detach后因为子线程与主线程分离,相当于进入了幕后,因此无法跟踪,就不能再使用join操作了。
  • 将join操作改为detach操作,我们发现有时候还是没有输出结果。这是因为main函数返回后有隐式调用的exit操作终止所有线程。因此并没有使得子线程完全独立。
  • 我们可以改成:将join改为detach后,在后面加上pthread_exit(0),只退出当前线程。输出为Hello, world!\n(stuck)
#include <pthread.h>
#include <stdio.h>
void *thread(void *vargp) {
    printf("Hello, world!\n"); 
    while(1);
    return NULL;
}
int main() {
    pthread_t tid;
    pthread_create(&tid, NULL, thread, NULL);
    pthread_detach(tid);
    pthread_exit(0);    // 只退出当前线程。
}

2.2 小结

  • 常用接口:pthread_create, pthread_join, pthread_detach, pthread_exit
  • 线程资源默认手动回收
    • 可以使用pthread_join回收其他子线程
    • 可以使用pthread_detach+pthread_exit来自动回收其他线程
    • 将exit改为pthread_exit来仅退出主线程。

3 线程

3.1 线程历史

略过,感兴趣自行STFW(search the fu***** fantastic web)。

3.2 多线程的进程

  • 一个进程可以拥有多个线程

  • 多线程进程可以跨处理器执行。

    • 调度基本单元从进程变成了线程。
    • 每个线程都有自己的执行状态。
    • 切换的单位从进程变成了线程。
  • 每个线程都有自己的栈

  • 内核中也有为线程准备的内核栈

  • 其他区域共享(数据,代码,堆)

img

3.3 对比进程与线程

  • 线程和进程的相似之处:

    • 都可以与其他进程/线程并发执行(可能在不同核心上)
    • 都可以进行切换
      • 引入线程后,调度管理单位由进程变为线程
  • 线程和进程的不同之处:

    • 同一进程的不同线程共享代码和部分数据
      • 不同进程不共享虚拟地址空间
    • 线程与进程相比开销较低
      • 进程控制(创建和回收)通常比线程更耗时
      • Linux的数据:
        • 创建和回收进程:~20K cycle
        • 创建和回收线程:~10K cycle(或更少)

4 TLS:线程本地存储

观察下面的程序:

#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
void *thread(void *vargp) {
    void *addr = malloc(0);
    printf("peer:errno=%d\n", errno);
    return NULL;
}
int main() {
    pthread_t tid;
    pthread_create(&tid, NULL, thread, NULL);
    void *addr = malloc(-1);
    pthread_join(tid, NULL);
    printf("main:errno=%d\n", errno);
}

我们会发现输出结果可能为两种情况:

# 第一种情况:
peer:errno=0
main:errno=0
# 第二种情况:
peer:errno=0
main:errno=12

前者是由于主线程执行后,再执行子线程导致的。errno为一个全局变量,因此子线程将其修改成了0。而后者先执行了子线程,再继续执行主线程。因此errno的值分别为0和12。这是因为每个线程具有不同的虚拟地址空间,这些不同的虚拟地址空间映射到了相同的真实物理地址。

5 线程的实现

5.1 进程控制块PCB到线程控制块TCB

  • PCB的部分内容转移到TCB

    • 每个线程TCB保存自己的处理器上下文,内核栈,退出/执行状态
    • PCB维护共享地址空间
    • PCB与TCB相互引用
  • 每个线程适应独立的内核栈

img

img

5.2 进行线程创建

比进程创建步骤少(无需加载可执行文件)。

  1. TCB相关内容初始化
  2. 维护进程,线程关系
  3. 准备运行环境。

img

Linux中采用clone(本用于创建进程)实现。
创建进程需要多个特殊标记。

  • CLONE_VM: 线程(进程?)共享同一地址空间
  • CLONE_THREAD: 新的线程(进程?)与原进程同时属于同一进程。

为什么这里会打上问号?因为Linux内部实际上并没有抽象一个新的模型来描述线程,而是以轻量级进程来将他们和线程相互关联起来。

5.3 进程退出与合并

不需要销毁虚拟地址空间vmspace

img
img

5.4 与进程管理接口的关系

  • 一个多线程的程序调用fork会出现什么情况?
    • 所有线程都被拷贝导致重复读/写同一个文件
  • 只拷贝了父进程中调用fork的线程
    • 新进程中只出现了一个线程,不会出现反直觉的重复操作
    • 其他线程内存状态被拷贝并且不被主动释放
  • posix:尽量不要使用fork拷贝多线程
    • 希望使用多进程:使用fork
    • 希望多线程:pthread_create

5.5 用户态线程与内核态线程

  • 根据线程是否受到内核管理将线程分为两类
    • 内核态线程:内核可见,受到内核管理
    • 用户态线程:内核不可见,不受内核直接管理
  • 内核态线程
    • 内核创建,线程相关信息存放在内核中
  • 用户态线程(纤程)
    • 在应用态创建,线程相关信息主要存放在应用数据中

5.6 线程模型

img

多对一模型
  • 将多个用户态线程映射给单一的内核线程
    • pros:管理内核简单
    • cons:可扩展性差,无法适应多核机器的发展
    • 主流操作系统中被弃用,用于各种用户态线程库中
一对一模型
  • 每个用户线程映射单一的内核线程
    • 优点:解决了多对一模型中的可扩展问题
    • 缺点:数量大,开销大
  • 主流OS采用
多对多模型(Scheduler Activation)
  • N个用户态线程映射到M个内核态进程(N>M)

    • 优点:解决了可扩展性问题(多对一)和线程过多问题(一对一)
    • 缺点:管理复杂
  • 虚拟化中广泛应用

5.7 TCB

  • 一对一的模型结构TCB氛围两部分
  • 内核态和PCB结构相似,进程和线程在linux中使用的是同一种数据结构,线程切换中使用
  • 应用态使用线程库定义。例如pthread结构体,可以认为是TCB(内核)的扩展

img

标签:10,Lec,线程,内核,pthread,进程,exit,NULL
From: https://www.cnblogs.com/mumujun12345/p/18665499

相关文章

  • Sigrity System SI SerialLink模式进行100base_T1协议仿真分析操作指导-100BaseT1_Rx
    SigritySystemSISerialLink模式进行100base_T1协议仿真分析操作指导-100BaseT1_RxSigritySystemSISerialLink模式提供了10个协议合规性检查工具模板,用户可以将根据实际应用替换模板中的SPICE文件,然后进行协议仿真分析,同时软件还提供了目标结果的模板MASK以及该协议需要......
  • 洛谷 P1102 A-B 数对(二分写法)
    题目:P1102A-B数对-洛谷|计算机科学教育新生态题目背景出题是一件痛苦的事情!相同的题目看多了也会有审美疲劳,于是我舍弃了大家所熟悉的A+BProblem,改用A-B了哈哈!题目描述给出一串正整数数列以及一个正整数 C,要求计算出所有满足 A−B=C 的数对的个数(不同位置的数......
  • 融合高斯扰动与竞争学习的改进型多目标部落竞争与成员合作算法(IMOCTCM)求解TP1-TP10及
    一、部落竞争与成员合作算法CTCM部落竞争与成员合作算法(Competitionoftribesandcooperationofmembersalgorithm,CTCM)由ChenZuyan等人于2024年提出的一种智能优化算法。该算法受古代部落之间竞争及其合作行为的启发而得。参考文献:[1]ZuyanChen,ShuaiLi,Ameer......
  • 2025年1月10日随笔
    公司人员调整,从个人发展而言确实缘分尽了,努力奋斗了三年。自己也看不到最后的理想结局,青春终究是怀有遗憾的。收拾心情,开始准备接下来的招聘了。在这家公司主要是sass以及APP内嵌H5产品的成长,确实不是自己最合适的路子,自己在这家公司也是磨练综合能力为主。接下来要突破的事情......
  • 鸿蒙面试 2025-01-10
    写了鉴权工具,你在项目中申请了那些权限?(常用权限)位置权限 :ohos.permission.LOCATION_IN_BACKGROUND:允许应用在后台访问位置信息。ohos.permission.LOCATION:允许应用访问精确的位置信息。ohos.permission.APPROXIMATELY_LOCATION:允许应用访问大致的位置信息。相机权限 :......
  • 2025.1.10(MyBatis知识点)
    多条件查询方式一:使用#{arg0}-#{argn}或者#{param1}-#{paramn}获取接口请求参数方式二:使用注解,接口中引入@Param(“name”)注解,再在配置文件中#{name}获取参数方式三:使用pojo对象传递参数,配置文件中按照实体类的属性获取参数模糊查询select*fromuserwhe......
  • 2024.12.10(SpringBoot知识点总结)
    3.1起步依赖原理分析3.1.1分析spring-boot-starter-parent按住Ctrl点击pom.xml中的spring-boot-starter-parent,跳转到了spring-boot-starter-parent的pom.xml,xml配置如下(只摘抄了部分重点配置):org.springframework.bootspring-boot-dependencies2.0.1.RELEASE../../spring......
  • 梦开始的地方:力扣热题100哈希表
    文章目录前言一、哈希表是什么二、力扣解题常见的三种哈希结构(java版本)1.数组2.set(集合)3.map(映射)总结前言在刷力扣100题的征程中,我从哈希相关题目入手,一路探索,收获颇丰。如今,想将自己在这一过程中的思路与感悟进行一番总结,既为记录成长,也希望能给同样在算法之路上......
  • 0-±10mA/0-±20mA/0-±5V/0-±10V正负信号隔离变送器
    0-±10mA/0-±20mA/0-±5V/0-±10V正负信号隔离变送器定义:是指通过技术手断采集各种正负电压电流信号,经过滤波,放大,转换,隔离后变送成标准的模拟量电压电流信号无失真远传给PLC,DCS,工业控制系统,数据采集卡等所需要的信号场合,实现工业自动化控制的小型仪器仪表设备.那么......
  • java-多线程(一)
    线程线程是操作体统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。并发和并行并发:在同一时刻,有多个指令在单个CPU上交替执行。并行:在同一时刻,有多个指令在多个cpu上同时执行。多线程的实现方式继承Thread类的方式进行实现。多线程的第一种启......