合肥市网站建设_网站建设公司_C#_seo优化
2025/12/20 10:23:58 网站建设 项目流程

1. 生产者与消费者(Producer‑Consumer)

场景

想象一家超市的“自助结账机”。

角色 说明
生产者 把刚进来的货物(数据)放进小车(共享缓冲区)
消费者 取走货物后卖给顾客(使用数据)
  • 小车不是无限大,它有 N 个格子(缓冲区)。
  • 生产者跟消费者不可能同时操作同一个格子。

潜在问题

  1. 空箱子:当小车空了,消费者就找不到任何东西,全部等着 “吃饭”。
  2. 满箱子:当小车装满了,生产者就再也放不下,全部停下来等。

如果没有任何同步手段,往往会出现

  • 频繁抢占:生产者和消费者争着抢同一个格子,导致写错数据。
  • 死循环:比方说生产者一次把多个格子搞满,消费者连条都收不到,两个都停不下来。

典型解决法:信号量 + 互斥锁

变量 含义 初始值 必要同步操作
empty (计数信号量) 空格子数 N empty.wait()(生产者先等空),empty.signal()(消费者放一格空)
full (计数信号量) 已装格子数 0 full.wait()(消费者先等满),full.signal()(生产者装完一格)
mutex (互斥锁) 保护缓冲区 mutex.lock()/unlock()

流程

  1. 生产者

    empty.wait();     // 等空 (若无空格子就阻塞)
    mutex.lock();     // 拿到盒子做事,确保同一时间只有一个人拿
    put(item);        // 把货物放进去
    mutex.unlock();
    full.signal();    // 通知有新货
    
  2. 消费者

    full.wait();      // 等满 (若没货就阻塞)
    mutex.lock();
    item = get();     // 取走货物
    mutex.unlock();
    empty.signal();   // 通知有空格子
    

简单说:
empty 代表还有多长的 “摆位”(空位)可以放东西。
full 代表有多少 “摆位”已经被占用(有货)。
两者各自忙着“等槽”与“放槽”,互不冲突。


2. 读者‑写者(Readers‑Writers)

场景

想象一个共享的图书库。

角色 说明
读者 只想看书,不需要做任何写入
写者 需要 挑选 书(稍微改一点)并写入新内容
  • 多名读者同时进来可以 一起 看同一本书。
  • 写者需要 排空书架:读者全部收回,写者拿走后再重新把书架换回来。

潜在问题

现象 结果
读写冲突 写者读完再写,读者看到半成品。
写者饿死 如果读者不断来,写者永远得不到空读者桌。
读者饿死 写者过多,读者只能等待。

典型解决法:读者计数 + 互斥锁 + 写者信号量

先想想:
为了让读者能“并行”,我们只需给【读书区】建一道闸门,读者闸门可同时通行,但写者闸门必须完全封闭。
方案要满足:

  1. 读者可并行
  2. 写者独占
  3. 读者 & 写者 互不与对方“打孔”
变量 含义 初始值 用法
mutex (互斥锁) 保护 readCount mutex.lock()/unlock()
writeLock (写信号量) 写者排他 1 writeLock.wait()/signal()
readCount (整型) 当前读者数量 0 读者进入/离开时增/减

流程

  1. 读者

    mutex.lock();          // 保护读者计数
    readCount += 1;
    if (readCount == 1) {writeLock.wait(); // 第一个读者阻塞写者
    }
    mutex.unlock();// ----- 读操作开始 -----  
    read();  
    // ----- 读操作结束 -----  mutex.lock();
    readCount -= 1;
    if (readCount == 0) {writeLock.signal(); // 最后一个读者放行写者
    }
    mutex.unlock();
    
  2. 写者

    writeLock.wait();   // 等 读者 离开
    write();            // 写操作
    writeLock.signal(); // 放行
    

解释:
writeLock 是“写者的门”,只有 零 人读时可以通过。
读者进来时:

  • 第一个读者点开门( writeLock.wait() ),随后其它读者直接穿行(不再触碰门)。
  • 最后一个读者点关门,放行。

这样就既让读者并行,又保证写者写前全量读者都离开,写后读者可以立刻进入。


3. 餐桌哲学家(Dining Philosophers)

场景

5 位哲学家围坐在桌子旁,桌面正好有 5 支叉子。

  • 每顿饭:哲学家需要 两支叉子(左右两边)才能吃面。
  • 叉子是共有资源,哲学家只是 “拿” 和 “放”,不是 “写” 或 “读”。

潜在问题:死锁

为什么会死锁?
想像所有哲学家同时拿起左右的一只叉子:

  1. 每个人都拿起自己的右边叉子(先起手)。
  2. 接下来每个人都要拿左边的叉子,却发现它被邻居抢走。
  3. 所有哲学家都在 等待,却没有人能继续动。

这就是经典“所有进程都等待对方释放资源”的死锁

简易解决方案

方案 思路 关键点
资源编号(顺序) 把叉子编号 1…N,所有哲学家 先拿编号较小的 再拿编号较大的 只需要保证 “拿 1 和 2” 的顺序相同即可(没有回环)
奇偶策略 令偶数哲学家先拿左叉子,奇数先拿右叉子 根本不可能所有人都按同一顺序拿,避免死锁
外部控制者(如“服务员”) 一位服务员只允许 最多 N-1 位哲学家同时拿叉子,再来的人等待 确保至少有一支叉子空闲,死锁不可能
随机放下 当哲学家拿到一只叉子后,如果放不到另一只就立刻把已拿的放下,稍后再试 破坏循环等待,可能导致无穷循环(但不会死锁)

最常见演示实现:先把 第 5 位哲学家 改成 “先拿右叉子后拿左叉子”,其余三位保持原方式。
这样三位哲学家并行,而第五位在等待时必定至少有一只叉子是空闲的,谁也得不到饥饿死锁的机会。

代码(Java 伪作示例)

class Philosopher implements Runnable {private final int id;private final Fork left, right;public Philosopher(int id, Fork left, Fork right) {this.id = id;this.left = left;this.right = right;}public void run() {try {for (;;) {think();                     // 思考if (id % 2 == 0) {           // 偶数先拿左,奇数先拿右left.pick();right.pick();} else {right.pick();left.pick();}eat();                       // 吃饭left.put();                  // 放左右叉子right.put();}} catch (InterruptedException ignored) { }}
}

Fork类内部使用 synchronizedReentrantLock


小结:同步的三大核心概念

概念 解释 例子
互斥(Mutual Exclusion) 同一时刻只能有一个线程访问共享资源。 mutex 只让一个线程“打开门”。
序列化(Ordering) 规定资源获取的顺序,避免循环等待。 资源编号、奇偶顺序。
等待与唤醒 线程在资源不可用时挂起,有资源时被唤醒。 信号量的 wait()/signal()

只要能把 “谁先拿?谁等?” 和 “谁等待” 这两件事做得彻底,任何同步问题都能化解。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询