ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Swift Algorithm Club 实战:用 GCD 信号量在 Swift 中实现哲学家就餐问题(Chandy/Misra 解法)

Swift Algorithm Club 实战:用 GCD 信号量在 Swift 中实现哲学家就餐问题(Chandy/Misra 解法) Swift Algorithm Club 实战用 GCD 信号量在 Swift 中实现哲学家就餐问题Chandy/Misra 解法【免费下载链接】swift-algorithm-clubAlgorithms and data structures in Swift, with explanations!项目地址: https://gitcode.com/gh_mirrors/sw/swift-algorithm-club导读哲学家就餐问题Dining Philosophers Problem是并发编程领域最经典的同步问题之一用于演示死锁、饥饿等同步陷阱及其解决技术。本文以 swift-algorithm-club 仓库中 DiningPhilosophers 模块为核心完整讲解其基于 Chandy/Misra 分布式算法的解法并结合 Sources/main.swift 的源码逐行剖析 ForkPair、DispatchSemaphore 与 DispatchQueue 的配合方式。读完本文你将理解该问题为何会发生死锁、Chandy/Misra 算法如何用脏/干净叉子标签避免死锁与饥饿并能复现、扩展这一可同时构建于 macOS 与 Linux 的 Swift 并发示例。问题背景五个哲学家与五把叉子该问题最初由 Edsger Dijkstra 于 1965 年提出当时是作为学生考试练习表述为多台计算机竞争磁带驱动器外设访问权不久后 Tony Hoare 给出了如今广为流传的表述形式。问题设定如下五位沉默的哲学家围坐在一张圆桌旁桌上放着意大利面spaghetti。每两位相邻哲学家之间放着一把叉子即共有五把叉子。每位哲学家必须交替地进行思考与吃饭两个状态。哲学家只有在同时拿到左手和右手两把叉子时才能进食每把叉子同一时刻只能被一位哲学家持有。哲学家吃完饭必须放下两把叉子使其对其他哲学家可用。叉子可以在可用时随时被取走但哲学家在集齐两把叉子之前不能开始吃饭。假设意大利面无限供应、食欲无限吃饭不受食物量或胃容量限制。核心难题在于设计一套行为纪律即并发算法使得没有任何哲学家会饿死——每个人都能永远在吃饭与思考之间循环同时任何哲学家都无法预知他人何时想吃或想思考。从并发理论角度看若五位哲学家同时各自拿起左手叉子就会陷入每人持有一把叉子、又都在等待右手叉子的循环等待状态即经典死锁。这正是该问题要揭示的同步风险。解决方案选型Chandy/Misra 分布式算法针对该问题存在多种经典解法如资源分级、服务生仲裁等。本仓库的实现选用的是Chandy/Misra 解法其特点如下完全分布式允许多个代理agent在完全没有中央权威控制加锁与资源序列化的前提下竞争任意数量的资源高并发度算法天然支持大规模问题叉子与哲学家数量可任意扩展消除饥饿通过叉子的脏/干净标签将偏好倾斜给最饥饿的进程而刚吃过的进程处于劣势类似不允许哲学家连续吃两次而不把叉子让给别人的规则但比该规则更灵活代价该解法违反原问题哲学家之间不说话的假设——因为算法依赖请求消息request message在哲学家之间通信。算法描述脏叉子、干净叉子与请求消息Chandy/Misra 算法的核心状态机如下初始化为每一对竞争同一资源的哲学家创建一把叉子并交给编号较低的那位哲学家哲学家记为 Pn编号为 n。每把叉子初始均为脏dirty状态。请求阶段当某位哲学家需要使用一组资源即进食时必须向所有竞争邻居发送消息请求所需的全部叉子。响应规则收到请求消息的哲学家若手中叉子是干净clean的则保留若是脏的则必须交出。交出前需要先将叉子清洗clean。释放阶段哲学家吃完饭后其所有叉子都变为脏状态。若此前有其他哲学家请求过其中某把叉子这位刚吃完饭的哲学家需要清洗该叉子并发送给对方。为什么能避免死锁基于 Chandy 与 Misra 的分析由叉子的分布及其脏/干净状态可以推导出一个偏好级别系统system of preference levels。该系统可以描述为一幅有向无环图acyclic graph只要初始图是无环的协议的运行过程就不会把无环图变成有环图从而从理论上保证死锁不会发生。这里有一个关键前提初始化状态必须打破对称性。如果系统初始化为完全对称的状态——例如所有哲学家都持有自己左侧的叉子——那么偏好图一开始就是环形的算法无法阻止死锁。因此让编号较低的哲学家初始持有脏叉子可以保证初始图无环。这也解释了本实现中叉子按索引排序取用与低编号优先持有的设计意图。Swift 实现架构本实现由 Jacopo Mangiavacchi 编写后经 Bruno Scheele 完成 Swift 4.2 兼容性检查。README 标注为 Swift 3.0 实现基于 GCDGrand Central DispatchSwift 跨平台 libdispatch与信号量DispatchSemaphore技术可同时构建于 macOS 与 Linux。源码中保留了#if swift(4.0)的条件编译与#available(macOS 10.10, *)可用性检查说明其兼容现代 Swift 编译器。整体架构分为三个层次层次载体职责资源层ForkPair.forksSemaphore静态数组用 N 个初始值为 1 的DispatchSemaphore表示 N 把叉子每把叉子同一时刻只能被一个哲学家持有实体层Philosophers结构体为每位哲学家关联一对左右叉子实现拿起/放下动作与无限思考-吃饭循环调度层后台DispatchQueue 全局DispatchSemaphore让每位哲学家在后台队列异步并发运行主线程用值为 0 的信号量永久等待使程序持续运行ForkPair叉子对与防死锁排序ForkPair是资源层的核心结构体main.swiftstruct ForkPair { static let forksSemaphore: [DispatchSemaphore] Array(repeating: DispatchSemaphore(value: 1), count: numberOfPhilosophers) let leftFork: DispatchSemaphore let rightFork: DispatchSemaphore init(leftIndex: Int, rightIndex: Int) { //Order forks by index to prevent deadlock if leftIndex rightIndex { leftFork ForkPair.forksSemaphore[leftIndex] rightFork ForkPair.forksSemaphore[rightIndex] } else { leftFork ForkPair.forksSemaphore[rightIndex] rightFork ForkPair.forksSemaphore[leftIndex] } } func pickUp() { //Acquire by starting with the lower index leftFork.wait() rightFork.wait() } func putDown() { //The order does not matter here leftFork.signal() rightFork.signal() } }关键点forksSemaphore是静态数组N 个哲学家共享 N 把叉子DispatchSemaphore(value: 1)表示叉子可用对应互斥锁语义。构造函数按索引排序后再赋值给 left/rightpickUp()始终从低索引叉子开始获取。这是经典的资源排序法resource ordering所有哲学家按统一顺序拿叉子可避免循环等待从源头上消解死锁。pickUp()对两把叉子依次wait()信号量计数减 1为 0 时阻塞putDown()依次signal()计数加 1唤醒等待者。释放顺序无关紧要因为释放永远不会导致死锁。Philosophers哲学家实体与运行循环Philosophers结构体main.swift为每位哲学家计算左右叉子索引并绑定一对信号量struct Philosophers { let forkPair: ForkPair let philosopherIndex: Int var leftIndex -1 var rightIndex -1 init(philosopherIndex: Int) { leftIndex philosopherIndex rightIndex philosopherIndex - 1 if rightIndex 0 { rightIndex numberOfPhilosophers } self.forkPair ForkPair(leftIndex: leftIndex, rightIndex: rightIndex) self.philosopherIndex philosopherIndex print(Philosopher: \(philosopherIndex) left: \(leftIndex) right: \(rightIndex)) } func run() { while true { print(Acquiring lock for Philosopher: \(philosopherIndex) Left:\(leftIndex) Right:\(rightIndex)) forkPair.pickUp() print(Start Eating Philosopher: \(philosopherIndex)) //sleep(1000) print(Releasing lock for Philosopher: \(philosopherIndex) Left:\(leftIndex) Right:\(rightIndex)) forkPair.putDown() } } }要点索引映射哲学家 Pn 的左手叉子索引为 n右手叉子索引为 n-1右手索引为 -1 时回绕为 N-1圆桌。run()是无限循环取叉 → 进食 → 放叉循环往复即永远交替吃饭与思考的行为模型。被注释掉的sleep(1000)处可自行插入模拟思考/进食耗时的代码。四个print语句输出哲学家编号与左右叉子索引便于在控制台观察加锁、进食与释放的顺序。主调度后台队列与永久等待程序入口的调度逻辑main.swift// Layout of the table (P philosopher, f fork) for 4 Philosophers // P0 // f3 f0 // P3 P1 // f2 f1 // P2 let globalSem DispatchSemaphore(value: 0) for i in 0..numberOfPhilosophers { if #available(macOS 10.10, *) { DispatchQueue.global(qos: .background).async { let p Philosophers(philosopherIndex: i) p.run() } } } //Start the thread signaling the semaphore for semaphore in ForkPair.forksSemaphore { semaphore.signal() } //Wait forever globalSem.wait()这里值得注意的实现细节本示例默认numberOfPhilosophers 4main.swift注释中画出了 P0P3 与 f0f3 的圆桌布局即 4 位哲学家、4 把叉子按 README 的说法该算法可扩展为任意数量。每位哲学家通过DispatchQueue.global(qos: .background).async在系统后台队列中异步并发执行互不阻塞。循环结束后对每把叉子的信号量执行一次signal()使所有叉子初始计数为 1可用。globalSem DispatchSemaphore(value: 0)作为主线程永久挂起的闸门主线程globalSem.wait()后无人 signal进程便一直存活让后台哲学家无限循环下去。运行与验证该模块同时提供了两种工程组织方式便于在不同环境运行Xcode 工程DiningPhilosophers.xcodeproj 定义了名为 DiningPhilosophers 的可执行工具 targetproductType 为com.apple.product-type.tool源码仅包含 Sources/main.swift 一个文件其 Scheme 默认使用 Debug 配置、LLDB 调试器直接运行。Swift Package Manager 清单Package.swift 声明了名为 DiningPhilosophers 的包。按 SwiftPM 的目录约定源码位于Sources/下可在支持 Swift 的 Linux 环境执行swift build构建、swift run运行。运行后控制台会持续输出类似下面的日志流展示哲学家获取锁、开始进食、释放锁的过程Philosopher: 0 left: 0 right: 3 Philosopher: 1 left: 1 right: 0 Philosopher: 2 left: 2 right: 1 Philosopher: 3 left: 3 right: 2 Acquiring lock for Philosopher: 0 Left:0 Right:3 Start Eating Philosopher: 0 Releasing lock for Philosopher: 0 Left:0 Right:3 ...以上输出格式由 main.swift 的打印语句决定实际顺序取决于线程调度。值得注意的实现差异与边界从源码结构看本实现与 README 描述的 Chandy/Misra 算法存在两处重要差异理解它们有助于避免误用以静态资源排序代替动态请求消息Chandy/Misra 原始算法依赖哲学家之间的请求消息与脏/干净标签动态传递叉子而本实现的ForkPair直接对所有哲学家统一按叉子索引排序取用属于资源排序策略。其实际防死锁依据是所有人按同一顺序加锁而非偏好图无环性的动态推导。README 中完全分布式、无中央权威的表述对应的是算法设计层面的理念代码层面则更接近集中式排序加锁。脏/干净标签未在代码中显式建模README 详细描述的脏叉子必须交出、干净叉子可保留、吃完后变脏规则在 main.swift 中并未出现对应的状态字段或消息传递逻辑。若要严格复现 Chandy/Misra 的分布式消息协议需要在Philosophers中补充叉子状态与请求处理逻辑。此外无论采用哪种变体初始化都必须打破对称性例如让低编号哲学家优先持有叉子否则算法无法阻止死锁——这是 README 中反复强调的前提条件。总结哲学家就餐问题是理解并发同步的经典入门案例。swift-algorithm-club 的 DiningPhilosophers 模块用不到 110 行 Swift 代码借助DispatchSemaphore与DispatchQueue演示了资源排序防死锁 信号量互斥的完整思路ForkPair静态信号量数组代表叉子、按索引排序取用消解循环等待、后台队列并发执行哲学家、全局信号量挂起主线程。它既是一个可运行的跨平台示例也是学习 GCD 信号量 APIwait/signal、资源排序法与分布式哲学家问题Chandy/Misra理论的最佳配套实验代码。【免费下载链接】swift-algorithm-clubAlgorithms and data structures in Swift, with explanations!项目地址: https://gitcode.com/gh_mirrors/sw/swift-algorithm-club创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表