2012 不宜进入的三个技术点(中)

本文探讨了传统线程在并发处理上的局限性,介绍了绿色线程及其优势,并深入分析了Rust等现代语言如何改进并发模型,实现高效、安全的并发编程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

赖勇浩(http://laiyonghao.com

线程

线程是指进程中的一个单一顺序的控制流,是操作系统能够调度的最小单位,一个进程中可以有多条线程,分别执行不同的任务。线程有内核线程和用户线程之分,但在本文中仅指内核线程。在软件开发中,使用线程有以下好处:
1、在多核或多路 CPU 的机器上多线程程序能够并发执行,提高运算速度;
2、把 I/O,人机交互等与密集运算部分分离,提升 I/O 吞吐量和增进用户体验。
线程的缺点也很明显:
1、创建一条线程需要较大的内存开销,导致不能创建海量的线程;
2、线程由操作系统调度(分配时间片),线程切换的 CPU 成本比较高,导致大量线程存在时大量 CPU 资源消耗在线程切换上;
3、同一进程的多条线程共享全部系统资源,在多线程间共享资源需要进入加锁,大量的锁开销不提,重要的是加大了编写程序的复杂性,这一点你看看有多少书名含有“多线程”三个字就明白写个多线程应用有多难了;
4、I/O 方面,多线程帮助有限,以 TCP Socket Server 为例,如果每一个 client connection 由一条专属的线程服务,那么这个 server 可能并发量很难超过 1000。为了进一步解决并发带来的问题,现代服务器都使用 event-driven i/o 了。
event-driven i/o 解决了并发量的问题,但引入了“代码被回调函数分割得零零碎碎”的问题。特别是当 event-driven i/o 跟 multi-threading 结合在一起的时候,麻烦就倍增了。解决这个问题的办法就使用绿色线程,绿色线程可以在同一个进程中成千上万地存在,从而可以在异步 I/O 上封装出同步的 APIs,典型的就是用基于 greenlet + libevent 开发的 python 库 gevent。绿色线程的缺陷在于操作系统不知道它的存在,需要用户进行调度,也就无法利用到多核或多路 CPU 了。为了解决这个问题,很多大牛都做出了巨大的努力,并且成果斐然,scala、google go 和 rust 都较好地解决了问题,下文以 rust 的并发模型为例讲一下。
rust 提出一个 Task 的概念,Task 有一个入口函数,也有自己的栈,并拥有进程堆内存的一部分,为方便理解,你可以把它看作一条绿色线程。rust 进程可以创建成千上万个 Tasks,它们由内建的调度器进行调度,因为 Tasks 之间并不共享数据,只通过 channels/ports 通信,所以它们是可并行程度很高。rust 程序启动时会生成若干条(数量由 CPU 核数决定或运行时指定)线程,这些线程并行执行 Tasks,从而利用多个 CPU 核心。

如上图,rust 应用程序不停地 spawn 出一个又一个 Tasks,它们由 tasks 调度器管理,在适当的时机,调度器会把某一个 Task 分配给原生线程执行,如果这个 Task 进入 I/O 等待或主动让出 CPU(sleep),那么这个 Task 会被交回给调度器,而相应的原生线程会执行另一个新分派的 Task。尽管使用 rust 编程语言是不能创建线程的(直接调用 C 函数不算),但 rust 应用程序实际上是多线程的(一般情况下),它能够充分地利用多核或多路 CPU。
综上,类似 rust 的 Task 的概念是比线程更好的并发模型,更安全,编写的代码也更加容易维护(关于维护性,我相信写过 gevent 程度或 go 程序的同学会认同的)。线程当然不会消亡,但随着 scala/go/rust 的成熟,在可以预见的将来,线程会退到它呆着的角落:远离普通程序员,只有少数人需要了解它的细节。

<think>嗯,用户想了解中断处理函数执行时间过长的原因和影响。这个问题涉及到操作系统和嵌入式系统的核心机制,需要从硬件响应、调度机制和实时性几个角度解释。 用户可能正在学习操作系统内核开发或者嵌入式编程,遇到了中断处理相关的性能问题。从引用[1]和[2]来看,用户已经接触过系统调度和单片机串口中断的案例,但需要更系统的理论解释。 核心问题其实有三个层面:首先是硬件层面,中断本质是抢占CPU的硬件信号,长时间不返回会导致中断丢失(如串口数据覆盖);其次是系统层面,调度器在中断期间被冻结,所有进程都会"卡住";最后是实时性要求,像RTC秒中断这样的场景必须及时响应。 特别要注意引用[1]提到的"调度关闭"机制——这是理解整个问题的关键。当CPU处于中断上下文时,内核会暂时禁用调度器,就像交通警察在路口处理事故时让所有车辆暂停通行。如果事故处理太久,整条路就会瘫痪。这个比喻可能帮助用户理解。 用户提供的引用[3]中那个STM32的案例很典型,在RTC中断里做串口发送导致系统卡死,正好印证了理论。应该强调这种错误在实际开发中很常见,建议用标志位+主循环处理的模式替代。 最后需要提醒用户,虽然引用[4]提到JavaScript单线程机制有相似性,但硬件中断的不可打断性更极端。浏览器至少还有事件队列,而硬件中断是直接剥夺CPU控制权的。</think>中断处理函数执行时间过长会引发严重后果,主要原因和影响如下: --- ### **一、核心原因** 1. **中断上下文特殊性** - **调度被禁用**:进入中断时系统会关闭调度器(避免嵌套中断导致状态混乱),此时CPU无法切换进程[^1]。 - **无法睡眠/阻塞**:若在中断中调用睡眠函数(如`msleep()`),系统因调度关闭无法唤醒其他进程,直接导致死锁[^1]。 2. **硬件中断机制限制** - **中断抢占性**:高优先级中断可抢占低优先级中断,但同级/低级中断会被阻塞。 - **中断丢失风险**:若当前中断未及时返回,新中断可能被丢弃(如串口数据覆盖未处理的数据)[^2]。 --- ### **二、主要影响** | **影响类型** | **具体表现** | |--------------------|-----------------------------------------------------------------------------| | **系统崩溃** | 中断中调用阻塞函数 → 调度器无法运行 → 系统死锁[^1] | | **数据丢失** | 新中断数据覆盖未处理的旧数据(如串口接收缓存区被覆盖)[^2] | | **实时性破坏** | 高优先级任务(如电机控制、传感器采样)无法及时响应 | | **资源耗尽** | 中断嵌套过深 → 栈溢出(STM32等单片机常见问题)[^3] | | **性能劣化** | CPU长时间陷于中断 → 用户进程无法执行 → 系统卡顿 | --- ### **三、典型案例分析** 1. **串口中断延迟处理** - **场景**:在串口中断函数中延时10秒 - **后果**:后续数据持续覆盖缓冲区 → 仅能读取最后一次接收的数据[^2]。 - **原理**: ```c void USART_IRQHandler() { byte = USART_ReceiveData(); // 读取字节 delay(10000); // 延时导致新数据覆盖缓冲区 process(byte); // 实际处理的可能是被覆盖后的数据 } ``` 2. **RTC中断中调用耗时操作** - **场景**:在STM32的RTC秒中断中发送长串口数据 - **后果**:中断阻塞导致定时精度失效,后续中断堆积引发系统卡死[^3]。 --- ### **四、解决方案** 1. **中断设计原则** - **快进快出**:仅做关键状态记录(如设置标志位、复制数据到缓存区)。 - **任务分离**:耗时操作移交主循环或内核线程(如Linux的`tasklet`或`workqueue`)[^1]。 2. **优化技术** - **硬件缓冲**:启用串口FIFO缓冲多字节数据,减少中断触发频率[^2]。 - **中断嵌套**:合理配置中断优先级(仅限支持硬件嵌套的MCU)。 - **轮询混合**:非实时任务改用轮询模式(如查询I²C状态寄存器)。 --- ### **总结** > 中断的本质是**紧急事务响应**,而非任务处理。其执行时间需控制在微秒级,否则将破坏系统实时性并引发连锁故障。开发者应严格遵循"中断瘦身"原则,将复杂逻辑移至主循环或内核线程[^1][^2]。 ---
评论 56
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值