Lec 10 线程

news/2025/3/11 6:19:48/文章来源:https://www.cnblogs.com/mumujun12345/p/18665499

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/867724.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024-12-10-json

Json在pox.xml中添加如下依赖 <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.78</version> </dependency> 添加上述依赖后可用…

AtCoder Beginner Contest 387

A - Happy New Year 2025 题意给定正整数\(A,B\),求\((A+B)^2\)思路模拟代码点击查看代码 #include <bits/stdc++.h> using namespace std; #define int long long typedef pair<int, int> pii;const int mxn = 1e6 + 5;void solve() {int a, b;cin >> a &g…

班迪录屏电脑软件 Bandicam v8.0.1.2512 绿色便携解锁版

点击上方蓝字睿共享资源关注我 前言 Bandicam绿色便携版是一个很棒的视频录制软件,被很多人认为是世界上最好的三个视频录制工具之一。它的好处在于,即使你的电脑配置不是很高,或者你的电脑已经用了很多年,它都能运行得很顺畅。而且,当你用它来录制视频时,声音和画面会完…

摘樱桃II

摘樱桃II “作为一个合格的程序员,理应具有修bug修到凌晨4点的魄力” 戳我查看原题。 题目大意给定一个矩阵,矩阵中的每个数代表该点的樱桃个数。Robot1、Robot2分别从左上角与右上角出发,每次只能选择向正下方、左下方、右下方三个方向移动去采摘樱桃,到达矩阵的最后一行终…

colab上传压缩包文件,出现Unexpected end of archive问题?

Unexpected end of archive,上传图片数据集压缩包到colab上,使用命令进行解压,出现EOF错误,为啥呢?

BurpSuite实操之定序器功能使用

定序器的使用 BurpSuite的定序器是一款用于检测数据样本随机性质量的工具,通常用于检测访问令牌(sessiontoken)是否可预测、密码重置令牌是否可预测等场景,通过Sequencer的数据样本分析,能很好地降低这些关键数据被伪造的风险。操作:令牌保存到本地后查看: 我们看到token每…

G74【模板】拉格朗日插值法

视频链接:G74【模板】拉格朗日插值法_哔哩哔哩_bilibili P4781 【模板】拉格朗日插值 - 洛谷 | 计算机科学教育新生态// 拉格朗日插值法 O(n^2) #include <iostream> #include <cstring> #include <algorithm> using namespace std;#define LL long long …

【web安全】面向Web安全防护的蜜罐技术研究

摘 要 传统Web安全防护技术存在误报、漏报以及防御被动等问题,蜜罐技术的引入可有效改善此状况。本文针对面向Web安全防护的蜜罐技术进行研究,分析当前主流的Web蜜罐技术,提出蜜罐技术在Web安全防护中的应用模型,并进行了研究展望。 一、引言 随着Web2.0的发展,越来越多的…

文件筛选与提取、递归解压工具RecursiveDecompression

RecursiveDecompression是我用C#开发的一款实用工具,主要包括文件提取、递归解压缩两个功能。 假设我要把 D:\Temp\CalcNotepad 这个路径里面所有扩展名为vb的文件复制到另一个地方,一个一个复制很麻烦。 打开RD工具,选择源文件夹,然后选择目标路径D:\Test1(提前创建一个…

IT 运维服务规范(模板参考)

一、 总则 本部分规定了 IT 运维服务支撑系统的应用需求,包括 IT 运维服务模型与模式、 IT 运维服务管理体系、以及 IT 运维服务和管理能力评估与提升途径。 二、 参考标准 下列文件中的条款通过本部分的引用而成为本部分的条款。凡是注日期的引用文件,其随后所有的修改单(不…

Leetcode刷题的一些记录(Java)

Leetcode刷题 一、理论: 1. 数组: https://programmercarl.com/数组理论基础.html C++中二维数组在地址空间上是连续的。 像Java是没有指针的,同时也不对程序员暴露其元素的地址,寻址操作完全交给虚拟机。 所以看不到每个元素的地址情况,这里我以Java为例,也做一个实验。…