20、并发软件与线程调度全解析

并发软件与线程调度全解析

1. 并发软件中的资源保护机制

在并发软件中,为了确保共享资源被安全地访问,避免不同线程之间的干扰,有多种资源保护机制可供选择。

1.1 自旋锁(Spin Locks)

自旋锁允许中断和任务切换保持启用状态,通过仲裁竞争的临界区执行,使得它们不会相互干扰。具体做法是为每个共享资源关联一个布尔标志。在进入临界区之前设置该标志,退出时清除。当标志被设置时,其他试图访问同一资源的线程会被阻塞。阻塞是通过一个紧密循环实现的,当标志被设置时,线程会在这个循环中“自旋”等待。

以下是使用自旋锁保护临界区的代码示例:

do {
    disable_interrupts();
    ok = !flag;
    flag = TRUE;
    enable_interrupts();
} while (!ok);
// 临界区代码
flag = FALSE;

对于抢占式内核,自旋锁循环通常会包含一个内核调用,让线程“睡眠”几毫秒后再重新测试标志,以减少频繁上下文切换的开销。对于非抢占式系统,这个调用是必需的,否则锁将永远不会被释放,程序会停止运行。

1.2 互斥对象(Mutex Objects)

自旋锁虽然允许无关代码继续执行,但内核需要不时地进行上下文切换来重新测试标志。上下文切换的开销比禁用和启用中断要大得多,并且在标志被锁定时不会产生有效的工作。互斥对象通过将测试和设置操作移到内核中,消除了这种开销,直到锁被释放才会切换回被阻塞的线程。

使用互斥对象协调进入临界区需要在临界区的两侧调用内核函数。在进入临界区之前,调用“pend”或“take”函数,使线程阻塞直到获取指定的互斥对象。当线程到达临界区的末尾时,调用“post”或“give”函数释放互斥对象,以便其他等待同一互斥对象的线程可以继续执行。任何时候只有一个线程可以拥有互斥对象,并且只有获取它的线程才能释放它。

1.3 信号量(Semaphores)

信号量与互斥对象类似,都可以使线程阻塞直到某个条件满足,但信号量通常用于管理事件,而不是仲裁对共享资源的访问。信号量提供了一种线程间的信号机制,一个线程作为“生产者”,另一个作为“消费者”。

信号量包含一个计数器,由生产者递增,消费者递减。计数器不能低于 0 或高于创建信号量时设置的某个值“N”。当计数器为 0 时,消费者会被阻塞,直到生产者执行“post”操作递增计数器。当计数器为 N 时,生产者会被阻塞,直到消费者执行“pend”操作递减计数器。

信号量的常见用途包括事件计数和管理一组相同的资源。“二进制信号量”是一种特殊情况,计数器只能为 0 或 1,比一般的“计数信号量”更常用。虽然二进制信号量可以代替互斥对象来仲裁对共享资源的访问,但有一个微妙但重要的区别:互斥对象的“pend”和“post”操作必须在同一个线程中调用,而信号量没有这种所有权的概念。

2. 并发软件问题解答

以下是一些关于并发软件的常见问题及解答:
| 问题 | 选项 | 答案 |
| — | — | — |
| 哪种方法适合保护短临界区? | (a) 禁用中断 (b) 禁用任务切换 (c) 自旋锁 (d) 互斥对象 (e) 信号量 | (c) |
| 哪种方法会导致不必要的上下文切换? | (a) 禁用中断 (b) 禁用任务切换 (c) 自旋锁 (d) 互斥对象 (e) 信号量 | (c) |
| 哪些方法可以保护线程和 ISR 之间共享的数据? | (a) 禁用中断 (b) 禁用任务切换 (c) 自旋锁 (d) 互斥对象 (e) 信号量 | (a)(c)(d)(e) |
| 在抢占式内核中,上下文切换在什么情况下必须保存和恢复浮点寄存器? | (a) 浮点寄存器必须始终保存和恢复 (b) 如果浮点运算仅在单个线程中使用 (c) 如果浮点运算在多个线程中使用 | (c) |
| 多线程应用中的每个线程是否必须有自己的栈? | 是/否 | 是 |
| 使用非抢占式内核时,上下文切换何时发生? | (a) 仅通过显式调用内核函数 (b) 仅由于硬件中断 (c) 以上两者之一 | (c) |
| 使用抢占式内核时,上下文切换何时发生? | (a) 仅通过显式调用内核函数 (b) 仅由于硬件中断 (c) 以上两者之一 | (c) |
| 哪种内核提供更好的系统响应时间? | (a) 非抢占式内核 (b) 抢占式内核 | (b) |
| 哪种情况在多线程应用中永远不会导致数据损坏? | (a) 两个线程都读取存储在内存中的共享数据结构 (b) 两个线程,一个读取,另一个写入存储在内存中的共享数据结构 (c) 两个线程都读写存储在内存中的共享数据结构 | (a) |
| 哪些可以是多线程应用中仲裁对共享资源访问的合适选择? | (a) 禁用/启用中断系统 (b) 禁用/启用上下文切换(通过内核函数调用) (c) 使用信号量或互斥对象(通过内核函数调用) (d) 自旋锁 | (a)(b)(c)(d) |
| 哪种实时内核对外界事件的整体响应时间更好? | (a) 抢占式 (b) 非抢占式(协作式) | (a) |
| 在以下哪种实时内核中可以准确预测上下文切换在代码中的发生位置? | (a) 抢占式 (b) 非抢占式(协作式) | (b) |
| 哪种实时内核需要插入对内核的“yield”调用? | (a) 抢占式 (b) 非抢占式(协作式) | (b) |
| 拥有临界区所需的最小线程数是多少? | - | 2 |
| 拥有临界区所需的最小共享资源数是多少? | - | 1 |

3. 线程调度基础

在多线程编程中,线程调度是一个重要的问题,它决定了线程如何竞争处理器时间。

3.1 线程状态

线程有三种初始状态:非活动(inactive)、运行(running)和就绪(ready)。在竞争处理器时间之前,线程需要通过调用内核函数向调度器注册。在注册之前,线程处于非活动状态。内核负责管理线程在这些状态之间的转换。

graph LR
    A[非活动] -->|激活| B[就绪]
    B -->|调度器选择| C[运行]
    C -->|时间片结束或其他原因| B
    C -->|线程终止| A
3.2 挂起线程(Pending Threads)

为了优化处理器利用率,调度器需要考虑到线程经常需要等待某些事件发生或共享资源可用。当这种情况发生时,调度器应该在第一个线程等待时运行另一个线程。为了解决这个问题,引入了第四种状态:挂起(pending)。

当运行的线程遇到需要等待特定条件满足的情况时,通过内核调用将其状态更改为挂起。当挂起条件最终满足时,再通过内核调用将其状态从挂起改为就绪。

graph LR
    A[运行] -->|等待资源或事件| B[挂起]
    B -->|资源可用或事件发生| C[就绪]
    C -->|调度器选择| A
3.3 上下文切换

上下文切换分为非抢占式和抢占式两种。

非抢占式上下文切换的过程如下:
1. 线程 A 正在运行,线程 B 处于挂起状态等待特定事件发生。
2. 事件发生触发中断服务例程(ISR),ISR 执行信号量“post”调用记录事件,内核将线程 B 的状态从挂起改为就绪。
3. ISR 返回,继续执行线程 A。
4. 线程 A 调用内核函数“yield”,调度器进行上下文切换到线程 B。

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    A([线程 A 运行]):::startend --> B(事件发生):::process
    B --> C(ISR 执行 post):::process
    C --> D(线程 B 从挂起变为就绪):::process
    D --> E(ISR 返回,线程 A 继续运行):::process
    E --> F(线程 A 调用 yield):::process
    F --> G([线程 B 运行]):::startend

抢占式内核中的线程不需要显式调用内核函数来让出处理器。在中断例程中进行上下文切换,使得抢占式内核能够立即激活处理事件的线程,提供了显著改善和可预测的响应时间,这对于依赖在特定期限内完成任务的实时系统至关重要。

4. 调度算法

常见的调度算法有时间片轮转调度和基于优先级的调度。

4.1 时间片轮转调度(Round-Robin Scheduling)

时间片轮转调度是一种简单的调度算法,它为每个线程分配相同的固定时间片。当一个线程的时间片用完时,调度器将控制权交给序列中的下一个线程,不断循环。这种调度算法将所有线程视为具有同等重要性,但在大多数实时应用中,某些线程比其他线程更重要。

4.2 基于优先级的调度(Priority-Based Scheduling)

实时系统的调度算法根据程序员为每个线程分配的优先级来选择运行的线程。优先级分为静态和动态两种,静态优先级在应用程序执行期间不会改变,而动态优先级对于使系统响应时间快速且可预测是必要的。

在基于优先级的调度中,可能会出现资源饥饿和优先级反转的问题。

  • 资源饥饿(Resource Starvation) :当一个线程由于无法获取所需的资源(通常是 CPU)而永远无法被调度运行时,就会发生资源饥饿。例如,一个高优先级线程如果从不让出或阻塞,会导致低优先级线程 CPU 饥饿。为了防止资源饥饿,调度器可以设计为以轮询方式循环处理相同优先级的线程。
  • 优先级反转(Priority Inversion) :高优先级线程本应优先于低优先级线程执行,但优先级反转会暂时破坏这种优先级顺序。当一个高优先级线程需要获取一个已被低优先级线程占用的共享资源时,高优先级线程会被阻塞,直到低优先级线程完成其临界区并释放资源。

优先级反转分为有界和无界两种:
- 有界优先级反转(Bounded Priority Inversion) :这种情况的持续时间不超过低优先级线程临界区的执行时间,因为低优先级线程临界区的执行时间应该设计为可预测、短且固定的。
- 无界优先级反转(Unbounded Priority Inversion) :当一个中等优先级线程在反转期间抢占了低优先级线程,会进一步延迟高优先级线程的执行。这种情况被称为“无界”,因为只要中等优先级线程有继续运行所需的所有资源,它就会一直运行,这与其他两个线程共享的资源无关,并且可能是不可预测的。解决无界优先级反转问题的常见方法有优先级继承协议(PIP)和优先级上限协议(PCP),它们都基于在运行时修改线程优先级的能力。

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    A([高优先级线程运行]):::startend --> B(请求共享资源):::process
    B --> C(资源被低优先级线程占用):::process
    C --> D([高优先级线程挂起]):::startend
    D --> E(低优先级线程完成临界区):::process
    E --> F(释放资源):::process
    F --> G([高优先级线程继续运行]):::startend

综上所述,在并发软件和线程调度中,我们需要根据不同的应用场景选择合适的资源保护机制和调度算法,以确保系统的性能和稳定性。同时,要注意避免资源饥饿和优先级反转等问题的发生,提高系统的可靠性和响应时间。

并发软件与线程调度全解析

5. 线程状态转换总结

线程在其生命周期中会经历多种状态,这些状态的转换对于理解线程调度至关重要。下面我们对线程状态及其转换进行详细总结。

线程的状态主要包括非活动(inactive)、就绪(ready)、运行(running)和挂起(pending)。各状态之间的转换关系如下表所示:
| 起始状态 | 终止状态 | 转换条件 |
| — | — | — |
| 非活动 | 就绪 | 线程通过调用内核函数向调度器注册 |
| 就绪 | 运行 | 调度器选择该线程运行 |
| 运行 | 就绪 | 时间片结束、线程主动调用 yield 或等待资源/事件 |
| 运行 | 非活动 | 线程终止 |
| 运行 | 挂起 | 线程需要等待特定条件满足,如资源可用或事件发生 |
| 挂起 | 就绪 | 挂起条件满足,通过内核调用改变状态 |

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    A([非活动]):::startend -->|激活| B(就绪):::process
    B -->|调度器选择| C(运行):::process
    C -->|时间片结束等| B
    C -->|线程终止| A
    C -->|等待资源/事件| D(挂起):::process
    D -->|资源可用/事件发生| B
6. 不同调度算法的对比

不同的调度算法适用于不同的应用场景,下面我们对时间片轮转调度和基于优先级的调度进行对比分析。

调度算法 特点 适用场景 优点 缺点
时间片轮转调度 为每个线程分配相同的固定时间片,依次循环执行 对线程重要性要求不高,各线程需要公平分配处理器时间的场景 实现简单,公平性好 无法区分线程的重要性,对于实时性要求高的线程支持不足
基于优先级的调度 根据线程的优先级选择运行的线程,优先级可分为静态和动态 实时系统,需要根据线程的重要性和紧急程度进行调度的场景 可以优先处理重要线程,提高系统的响应时间和性能 可能会出现资源饥饿和优先级反转问题
7. 资源保护机制的选择策略

在并发软件中,选择合适的资源保护机制对于确保系统的正确性和性能至关重要。下面我们给出一些选择资源保护机制的策略。

  • 短临界区 :对于短临界区,自旋锁是一个合适的选择。因为自旋锁的开销相对较小,在短时间内等待锁的释放不会造成太大的性能损失。例如,在一些简单的计数器操作中,可以使用自旋锁来保护共享资源。
  • 长临界区 :对于长临界区,互斥对象或信号量更为合适。互斥对象可以避免不必要的上下文切换,而信号量可以用于管理事件和资源的分配。例如,在文件读写操作中,可以使用互斥对象来保护共享文件资源。
  • 线程和 ISR 之间的共享数据 :为了保护线程和 ISR 之间的共享数据,可以使用禁用中断、自旋锁、互斥对象或信号量。禁用中断可以确保在临界区内不会被中断干扰,但会影响系统的响应时间;自旋锁、互斥对象和信号量可以在不同程度上平衡性能和安全性。
8. 解决优先级反转问题的详细步骤

优先级反转问题会影响系统的性能和可靠性,下面我们详细介绍解决优先级反转问题的两种常见方法:优先级继承协议(PIP)和优先级上限协议(PCP)。

8.1 优先级继承协议(PIP)

优先级继承协议的核心思想是当高优先级线程被低优先级线程阻塞时,低优先级线程临时继承高优先级线程的优先级,直到释放共享资源。具体步骤如下:
1. 高优先级线程请求共享资源,发现资源被低优先级线程占用,进入挂起状态。
2. 低优先级线程继承高优先级线程的优先级,继续执行。
3. 低优先级线程完成临界区,释放共享资源,恢复原来的优先级。
4. 高优先级线程获得资源,继续执行。

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    A([高优先级线程运行]):::startend --> B(请求共享资源):::process
    B --> C(资源被低优先级线程占用):::process
    C --> D([高优先级线程挂起]):::startend
    D --> E(低优先级线程继承高优先级):::process
    E --> F(低优先级线程完成临界区):::process
    F --> G(释放资源,恢复原优先级):::process
    G --> H([高优先级线程继续运行]):::startend
8.2 优先级上限协议(PCP)

优先级上限协议为每个共享资源分配一个优先级上限,当线程获取资源时,其优先级会提升到该资源的优先级上限。具体步骤如下:
1. 为每个共享资源分配一个优先级上限。
2. 线程请求共享资源时,检查其当前优先级是否低于资源的优先级上限。
3. 如果低于,将线程的优先级提升到资源的优先级上限。
4. 线程完成临界区,释放共享资源,恢复原来的优先级。

通过使用优先级继承协议和优先级上限协议,可以有效解决优先级反转问题,提高系统的性能和可靠性。

9. 并发软件的性能优化建议

为了提高并发软件的性能,我们可以采取以下一些优化建议:
- 合理选择调度算法 :根据应用场景的特点,选择合适的调度算法。对于实时系统,优先考虑基于优先级的调度;对于公平性要求较高的场景,可以选择时间片轮转调度。
- 减少上下文切换 :上下文切换会带来一定的开销,因此应尽量减少不必要的上下文切换。可以通过优化线程的设计和资源的分配,减少线程的等待时间和阻塞次数。
- 避免资源饥饿 :在设计系统时,要注意避免资源饥饿问题的发生。可以通过合理分配线程的优先级和资源,确保每个线程都有机会运行。
- 优化资源保护机制 :选择合适的资源保护机制,根据临界区的长短和共享资源的特点,选择自旋锁、互斥对象或信号量。同时,要注意避免死锁等问题的发生。

10. 总结与展望

在并发软件和线程调度中,我们介绍了多种资源保护机制、调度算法和线程状态转换。通过合理选择资源保护机制和调度算法,可以确保系统的正确性和性能;通过解决优先级反转等问题,可以提高系统的可靠性和响应时间。

未来,随着计算机技术的不断发展,并发软件和线程调度将面临更多的挑战和机遇。例如,多核处理器的广泛应用需要更高效的调度算法来充分利用多核资源;实时系统对响应时间和可靠性的要求越来越高,需要更先进的资源保护机制和调度策略。我们需要不断研究和创新,以应对这些挑战,推动并发软件和线程调度技术的发展。

总之,深入理解并发软件和线程调度的原理和方法,对于开发高效、可靠的软件系统具有重要意义。希望本文能够为读者提供一些有益的参考和启示。

内容概要:本文介绍了ENVI Deep Learning V1.0的操作教程,重点讲解了如何利用ENVI软件进行深度学习模型的训练应用,以实现遥感图像中特定目标(如集装箱)的自动提取。教程涵盖了从数据准备、标签图像创建、模型初始化训练,到执行分类及结果优化的完整流程,并介绍了精度评价通过ENVI Modeler实现一键化建模的方法。系统基于TensorFlow框架,采用ENVINet5(U-Net变体)架构,支持通过点、线、面ROI或分类图生成标签数据,适用于多/高光谱影像的单一类别特征提取。; 适合人群:具备遥感图像处理基础,熟悉ENVI软件操作,从事地理信息、测绘、环境监测等相关领域的技术人员或研究人员,尤其是希望将深度学习技术应用于遥感目标识别的初学者实践者。; 使用场景及目标:①在遥感影像中自动识别和提取特定地物目标(如车辆、建筑、道路、集装箱等);②掌握ENVI环境下深度学习模型的训练流程关键参数设置(如Patch Size、Epochs、Class Weight等);③通过模型调优结果反馈提升分类精度,实现高效自动化信息提取。; 阅读建议:建议结合实际遥感项目边学边练,重点关注标签数据制作、模型参数配置结果后处理环节,充分利用ENVI Modeler进行自动化建模参数优化,同时注意软硬件环境(特别是NVIDIA GPU)的配置要求以保障训练效率。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值