程序在操作系统中的两种执行方式——顺序执行与并发执行,并通过对比突出了并发执行的复杂性及其带来的挑战

程序在操作系统中的两种执行方式——顺序执行与并发执行,并通过对比突出了并发执行的复杂性及其带来的挑战。以下是详细解析:

  1. 程序的顺序执行

    • 顺序性:程序严格按照代码书写的逻辑顺序执行,即“输入→计算→输出”,每一步都依赖前一步完成。
    • 封闭性:程序独占资源(如CPU、内存),运行过程中不受其他程序干扰,环境可预测。
    • 可再现性:只要输入相同,无论何时运行,结果始终一致,便于调试和验证。
  2. 程序的并发执行
    并发是现代操作系统的核心特性之一,允许多个程序或进程“同时”运行(物理上可能是交替执行)。

    • 实现条件:借助多道程序设计技术,多个程序驻留内存,由操作系统调度,在CPU和I/O设备上交替或并行执行。
    • 特征分析
      • 失去封闭性:由于资源共享(如内存、文件、设备),一个程序的执行可能被另一个程序影响。
      • 执行活动不一一对应:程序的逻辑流程与其实际执行顺序可能不同,例如因调度延迟导致某段代码未及时执行。
      • 相互制约性:程序之间存在同步关系,如P₂需等待P₁完成某个操作才能继续,否则会出现逻辑错误。
  3. 并发执行示例:交通流量统计
    设有两个程序:

    • 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,因此这些操作所在的代码段就是临界区。

临界区问题的四个解决条件:

为确保并发程序正确运行,进入临界区的机制必须满足以下四点要求:

  1. 互斥性(Mutual Exclusion):任一时刻最多只有一个进程能执行临界区代码;
  2. 前进性(Progress):不允许无关进程阻塞临界区的进入请求,没有进程在临界区时,其他想进入的进程应能尽快进入;
  3. 有限等待(Bounded Waiting):任何想进入临界区的进程应在有限时间内获得许可,避免饥饿;
  4. 忙则等待:当有进程在临界区内执行时,其他试图进入的进程必须等待。

如何通过互斥机制解决资源竞争问题?

互斥机制的核心思想是:对共享资源的访问进行加锁控制,确保每次只有一个线程能够操作该资源。

常见实现方式包括:
  1. 软件方法(如 Peterson 算法)

    • 适用于两个进程的场景,利用标志位和轮转变量实现互斥。
    int turn;           // 轮到谁?
    int flag[2] = {0};  // flag[i] 表示进程 i 是否想进入
    
    // 进程 i 的代码结构
    flag[i] = 1;
    turn = j;
    while (flag[j] && turn == j);  // 等待
    // --- 临界区 ---
    flag[i] = 0;
    
  2. 硬件支持(关中断、原子指令)

    • 关中断:在单处理器系统中,进入临界区前关闭中断,防止被抢占(但不适合多核系统);
    • 使用原子操作(如 TestAndSet、CompareAndSwap),保证测试与设置操作不可分割。
  3. 操作系统提供的同步工具

    • 互斥锁(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()
      

总结

  • 临界区是并发编程中最容易出错的部分,必须通过互斥机制加以保护;
  • 实际开发中推荐使用高级同步原语(如互斥锁、信号量),由操作系统或编程语言运行时提供支持,安全且易于管理;
  • 正确设计临界区不仅能避免数据竞争,还能提升系统的稳定性和可预测性。
  • 在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bol5261

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值