程序在操作系统中的两种执行方式——顺序执行与并发执行,并通过对比突出了并发执行的复杂性及其带来的挑战。以下是详细解析:
-
程序的顺序执行
- 顺序性:程序严格按照代码书写的逻辑顺序执行,即“输入→计算→输出”,每一步都依赖前一步完成。
- 封闭性:程序独占资源(如CPU、内存),运行过程中不受其他程序干扰,环境可预测。
- 可再现性:只要输入相同,无论何时运行,结果始终一致,便于调试和验证。
-
程序的并发执行
并发是现代操作系统的核心特性之一,允许多个程序或进程“同时”运行(物理上可能是交替执行)。- 实现条件:借助多道程序设计技术,多个程序驻留内存,由操作系统调度,在CPU和I/O设备上交替或并行执行。
- 特征分析:
- 失去封闭性:由于资源共享(如内存、文件、设备),一个程序的执行可能被另一个程序影响。
- 执行活动不一一对应:程序的逻辑流程与其实际执行顺序可能不同,例如因调度延迟导致某段代码未及时执行。
- 相互制约性:程序之间存在同步关系,如P₂需等待P₁完成某个操作才能继续,否则会出现逻辑错误。
-
并发执行示例:交通流量统计
设有两个程序:- P₁:读取车辆计数器并打印统计结果;
- P₂:将计数器清零。
若无同步机制,可能出现以下情况:
- 正常情况:P₁先读数 → P₂清零 → 结果正确;
- 异常情况:P₂先清零 → P₁读数为0 → 统计失真;
- 或两者交叉执行,造成数据竞争(race condition)。
这说明并发执行引入了不确定性,必须通过同步机制(如互斥锁、信号量)来保证关键操作的原子性和顺序性。
✅ 总结:
顺序执行简单、可控,但资源利用率低;
并发执行提高了资源利用率和系统吞吐量,但带来了资源共享、状态干扰和执行顺序不确定等问题,因此需要引入进程管理、同步与互斥机制来保障程序正确运行。
# 模拟交通流量统计中的并发问题(无同步时的风险)
counter = 0 # 全局计数器
def P1_print_count():
print(f"当前车流量: {counter}") # 可能在清零后执行
def P2_reset_count():
global counter
counter = 0
# 场景模拟:假设车辆已计入 counter=5
counter = 5
P2_reset_count() # 清零
P1_print_count() # 输出 0 —— 错误!应先输出再清零
# 正确做法:使用互斥锁控制访问顺序
import threading
lock = threading.Lock()
def safe_P1():
with lock:
print(f"安全输出车流量: {counter}")
def safe_P2():
global counter
with lock:
counter = 0
临界区(Critical Section) 是指一段访问共享资源(如全局变量、文件、设备等)的代码,在该段代码中必须保证同一时刻只有一个进程或线程可以执行,否则会导致数据不一致或逻辑错误。例如在交通流量统计示例中,读取和清零计数器的操作都涉及共享变量 counter,因此这些操作所在的代码段就是临界区。
临界区问题的四个解决条件:
为确保并发程序正确运行,进入临界区的机制必须满足以下四点要求:
- 互斥性(Mutual Exclusion):任一时刻最多只有一个进程能执行临界区代码;
- 前进性(Progress):不允许无关进程阻塞临界区的进入请求,没有进程在临界区时,其他想进入的进程应能尽快进入;
- 有限等待(Bounded Waiting):任何想进入临界区的进程应在有限时间内获得许可,避免饥饿;
- 忙则等待:当有进程在临界区内执行时,其他试图进入的进程必须等待。
如何通过互斥机制解决资源竞争问题?
互斥机制的核心思想是:对共享资源的访问进行加锁控制,确保每次只有一个线程能够操作该资源。
常见实现方式包括:
-
软件方法(如 Peterson 算法)
- 适用于两个进程的场景,利用标志位和轮转变量实现互斥。
int turn; // 轮到谁? int flag[2] = {0}; // flag[i] 表示进程 i 是否想进入 // 进程 i 的代码结构 flag[i] = 1; turn = j; while (flag[j] && turn == j); // 等待 // --- 临界区 --- flag[i] = 0; -
硬件支持(关中断、原子指令)
- 关中断:在单处理器系统中,进入临界区前关闭中断,防止被抢占(但不适合多核系统);
- 使用原子操作(如 TestAndSet、CompareAndSwap),保证测试与设置操作不可分割。
-
操作系统提供的同步工具
- 互斥锁(Mutex Lock):
import threading lock = threading.Lock() def critical_section(): lock.acquire() # 请求进入临界区 try: # 访问共享资源 print("正在访问共享资源...") finally: lock.release() # 释放锁 - 信号量(Semaphore):
from threading import Semaphore sem = Semaphore(1) # 初始值为1,表示互斥信号量 def worker(): sem.acquire() try: print("线程进入临界区") finally: sem.release()
- 互斥锁(Mutex Lock):
✅ 总结:
- 临界区是并发编程中最容易出错的部分,必须通过互斥机制加以保护;
- 实际开发中推荐使用高级同步原语(如互斥锁、信号量),由操作系统或编程语言运行时提供支持,安全且易于管理;
- 正确设计临界区不仅能避免数据竞争,还能提升系统的稳定性和可预测性。



被折叠的 条评论
为什么被折叠?



