【发布时间】:2016-01-26 11:24:26
【问题描述】:
信号量的有效且必要的实现要求它是原子指令。
我在互联网上看到几个用户级 C 实现使用计数等变量或队列等数据结构实现信号量。但是,涉及变量的指令不会作为原子指令运行。那么任何人如何在用户级别 C 中实现信号量。
一个c库semaphore.h是如何实现信号量的?
【问题讨论】:
标签: c operating-system semaphore atomic locks
信号量的有效且必要的实现要求它是原子指令。
我在互联网上看到几个用户级 C 实现使用计数等变量或队列等数据结构实现信号量。但是,涉及变量的指令不会作为原子指令运行。那么任何人如何在用户级别 C 中实现信号量。
一个c库semaphore.h是如何实现信号量的?
【问题讨论】:
标签: c operating-system semaphore atomic locks
答案几乎肯定是“它没有”——相反,它会调用提供必要原子操作的内核服务。
【讨论】:
直到c11 在标准 C 中是不可能的。正如您所说,您需要的是 atomic 操作。 c11 最终指定它们,例如参见 stdatomic.h。
如果您使用的是旧版本的标准,则必须直接使用嵌入式汇编程序或依赖编译器的供应商特定扩展,例如参见GCC atomic builtins。当然,处理器支持内存屏障、检查和交换操作等指令。它们只是不能从纯c99 和更早的版本访问,因为并行执行不在标准范围内。
阅读 MartinJames 的评论后,我应该在此处添加说明:这仅适用于您在用户空间中实现所有线程的情况,因为信号量必须阻塞等待它的线程,因此如果线程由内核的调度程序管理(如例如 Linux 上的 pthreads 的情况),有必要进行系统调用。不在您的问题范围内,但原子操作可能仍然对于实现例如有趣无锁数据结构。
【讨论】:
您可以像这样简单地实现信号量操作:
void sema_post(atomic_uint *value) {
unsigned old = 0;
while (!atomic_compare_exchange_weak(value, &old, old + 1));
}
void sema_wait(atomic_uint *value) {
unsigned old = 1;
while (old == 0 || !atomic_compare_exchange_weak(value, &old, old - 1));
}
语义上没问题,但它确实在sema_wait 中忙于等待(旋转)。 (注意sema_post 是无锁的,尽管它也可能旋转。)相反,它应该休眠直到value 变为正数。这个问题不能用原子来解决,因为所有的原子操作都是非阻塞的。在这里,您需要操作系统内核的帮助。因此,高效的信号量可以使用基于原子的类似算法,但在两种情况下进入内核(有关此方法的更多详细信息,请参阅 Linux futex):
sema_wait:当它找到value == 0时,请求睡眠sema_post:当 value 从 0 增加到 1 时,请求唤醒另一个睡眠线程(如果有)一般来说,要在数据结构上实现无锁(使用原子)操作,要求每个操作都适用于任何状态。对于信号量,wait 不适用于值 0。
【讨论】: