正确销毁命名的 System V 信号量

Posted

技术标签:

【中文标题】正确销毁命名的 System V 信号量【英文标题】:Correctly destroying named System V semaphores 【发布时间】:2009-08-07 01:02:26 【问题描述】:

我正在使用命名的 System V 信号量来锁定我在 OSX 和 linux 上的所有应用程序中的文件。无论如何定义都不是最漂亮的 API。

它似乎有效,但我不太清楚在每个人都完成后如何正确销毁信号量。

一般逻辑是这样的:

创作:

[1] 线程或进程尝试使用 ftok() 为文件创建的 key_t 打开一个信号量集。 Set 包含 2 个信号量。 [2] 如果信号量集不存在,则使用 666 权限创建。 [3] “锁定”(信号量之一)设置为释放状态(值 1)。 [4] “引用计数”(同一集合中的另一个信号量)递增。

锁定/解锁:

为了锁定 [5],线程将 "Lock" 信号量的值减 1(撤消),因此如果它已经为零,则等待。为了解锁 [6],线程将其加一,从而允许其他人锁定它。

破坏:

[7] 尝试减少“引用计数”信号量(使用 IPC_NOWAIT 标志)。 [8] 检查其值为0,如果是[9] 则销毁信号量集。

(还有一层基于线程本地存储的逻辑,使锁在一个线程内递归。)

问题是:

如何同步步骤 [1] 和 [2]? (如果信号量集不存在,但在我们计算星星时,它是由其他人创建的,所以现在创建也会失败) 如何将步骤 [4] 与 [8] 同步,以免 [9] 过早杀死我? 还有其他竞争条件吗?

PS:虽然 POSIX 信号量有更好的 API,但我认为我无法在此处描述的 sem_inlink() 行为中存活:

调用 sem_open() 重新创建或 重新连接到信号量是指一个 sem_unlink() 之后的新信号量是 调用。

所以我没有办法释放它们......

【问题讨论】:

【参考方案1】:

几种方法:

首先,如果您的目标是锁定文件,那么使用文件锁定调用,例如flock(2)fcntl(2)+F_SETLK,而不是信号量。恕我直言,这是最好的方法。

第二,永远保留一个 sem。你是对的,这个提议是活泼的,你的措辞表明新的 sem 客户可能随时出现。您需要一个单独的同步机制,例如一个单独的、长期存在的 sem,来控制您真正关心的 sem 的创建/销毁。你可以变得异国情调,并将其与专用的“等待零”(mysembuf.sem_op := 0)驱逐舰结合起来,观察 refcount sem 并准备好IPC_RMID。呸。最好只拥有一个持久的二进制信号量,而不需要用户提供引用计数。

第三,使用 POSIX 命名的 sems。完成后忽略sem_unlink() 而只是简单地sem_close()(当然,在sem_post()ing 解锁之后!)。这在概念上类似于以前的方法——一个小的同步原语持续存在——但是,正如你所说,一个更简单的 API。你也不必处理SysV semaphores' fatal flaw。

【讨论】:

我不确定致命缺陷:信号量的创建是原子的(我希望?)。下一个进程实际上会得到它,或者在最坏的情况下调用创建失败(应该简单地尝试在循环中再次获取它)。然后直到创建过程释放它(设置为 1),其他人都会得到已经存在的 sem,如果尝试“锁定”(使用 -1)他们将等待。创建过程失去控制,但没关系,它们都是平等的。 (实际上这回答了我的一个问题,我只是不想循环:)) 至于永久保存一个 sem,在 linux 上,最大值通常非常小,大约 128 组。我只需要锁定几个文件,但我的单元测试很快就能完成——这就是我想在不使用它们时杀死它们的主要原因。 至于文件锁定,我环顾了一下,似乎一个进程的线程不能相互锁定,这意味着我必须保持某种全局状态并使用 pthread 同步保护它原语... @Eugene,回复:“致命缺陷”,这就是IPC_CREATSETVAL 之间的差距。线程 A 创建一个 sem,B 尝试在 A 初始化它之前使用它。回复:单元测试 sem 耗尽,查看代码会有所帮助。例如,不清楚为什么 setup()teardown() 不为您处理单个 sem 集...【参考方案2】:

这就是我最终要做的事情(此时这是一种荣誉问题,我不会离开,直到我有正确的代码,无论手头的任务是否需要它:))。

创作

尝试使用 3 个 sem 打开 [1] 现有 sem 集,如果失败尝试 [2] 创建一个。如果因为有人已经创建而无法创建,请返回 [1]。这个循环最终会退出,要么打开或创建 sem,要么因为我无法处理的错误,在这种情况下我拿球回家。 (我也有 N 次迭代的限制,以防万一:))。

3 个 sem 中的一个是有效负载,另一个是引用计数,第三个是引用计数的锁。 [2]锁初始化为0后,锁定状态。

保留

如果 sem 集是由 [2] 创建的,则所有 3 个 sem 的 semoped [3] 从 0 到 1。有效负载被释放,引用计数为 1,锁定被释放(不撤消)。 如果它被 [1] 打开,则获取锁 [4] (-1),引用计数递增 (+1) 并释放锁 (+1)。如果此时 lock 为零,这将阻塞。如果此 semop 因 sem 集在我们等待时在 [6] 处被破坏而失败,则保留失败,我们将一直返回到 [1]。此循环的迭代次数也有限。

发布

获取锁 [5](-1 等待),引用计数递减(-1 无等待)。如果这成功,那么如果 ref 计数现在为零,则 sem 集被破坏。否则 [6] 锁被释放 (+1)。如果因为 sem 集被破坏而获取锁失败 - 什么都不做。

在保留和释放之间,payload照常使用。

除了每组 2 个信号量的复杂性和开销之外,只有一个问题(现在我看到了致命的缺陷 :))——当创建者在 [2] 和 [3] 之间崩溃时。这将使所有客户端挂起。我可以在 linux 上使用定时等待并杀死孤立的信号量,但是 OSX,通常是愚蠢的自我,没有定时操作,所以我有点搞砸了......

*...去写自己的内核什么的...*

【讨论】:

以上是关于正确销毁命名的 System V 信号量的主要内容,如果未能解决你的问题,请参考以下文章

System V信号量

System V 信号量

进程间通信——System V IPC 之进程信号量

System V 信号量使用相关函数

System V 信号量倍增/减量

linux进程间通讯-System V IPC 信号量