我目前正在设计一个具有大规模同步模式的C ++跨平台(Linux / Windows)服务器应用程序。我在内部使用boost :: thread作为特定于操作系统的线程的抽象。我的问题是保护数据数组,数组的每个元素都受到独立读/写锁的保护。
我的数组包含 4096个元素。考虑到“Little Book of Semaphores”(第85页)中提出的“编写器优先级读者 - 编写者”问题的解决方案,我的应用程序每个数组元素需要5个信号量。这总共提供了大约20000个信号量(或者,相当于20000个互斥量+ 20000个条件变量)。
我的应用程序的另一个特点是在给定时间内,大多数信号量都不活动(通常有大约32个“客户端”线程在数千个信号量上等待/发送信号)。请注意,由于整个服务器在单个进程中运行,因此我使用轻量级,基于线程的信号量(不是进程间信号量)。
我的问题有两个:
是否建议在单个进程上在Linux和Windows上创建 20000个信号量?嗯,当然,我想情况并非如此......
如果不推荐这种做法,我可以使用什么技术来减少实际信号量的数量,例如:在1个实际信号量的顶部创建 N个“仿真信号量”?我认为这将是一个有趣的解决方案,因为我的大多数信号量在给定时间都处于非活动状态。
提前致谢!
深入研究Boost源代码,我发现:
这个原因对我来说似乎并不清楚。特别是,在Windows下使用“boost :: shared_mutex”的进程间对象对我来说似乎不太理想。
答案 0 :(得分:1)
不建议这样做。你不应该这样做,因为 在Windows中,它将消耗每个信号量1个句柄对象。一个过程 只能管理特定数量的Handles对象。线程/进程 和其他Windows对象可能需要使用Handle对象和will 如果他们不能崩溃。这与Linux中的类似 文件描述符概念。
将4096个元素拆分为30个(例如)140个元素 元素并为每个140组分配一个信号量。然后30 (在这个例子中)线程将尝试访问那些30集和 他们将根据每个140组信号量进行同步。
答案 1 :(得分:1)
我会从Windows的角度告诉你我对它的看法。我在为Windows编写服务器应用程序方面经验丰富。
首先,为单个进程创建20k信号量绝对没有问题。它是一个非常轻量级的内核对象。甚至是“进程间”信号量。
但是我看到你的设计存在另一个问题。您应该知道,对内核对象(例如信号量/互斥锁)执行的每个操作都涉及繁重的内核模式事务(a.k.a。系统调用)。即使根本没有碰撞,每次这样的调用都可能花费你大约2k个CPU周期。
因此,您可能会发现自己只是在调用同步方法时花费了大部分处理器时间。
相反,为了同步线程,可以使用互锁操作。它们的成本要低得多(通常是几十个CPU周期)。
还有一个名为 critical section 的对象。它是一种互锁操作数和内核对象的混合体(如果存在实际碰撞则使用它)。您应该检查通常锁定元素的时间。如果它通常是一个短持续时间的锁 - 只需使用关键部分,忘记复杂的读写锁。
如果您处理长时间锁定,并且执行需要读写锁定,并且您发现在内核模式事务中花费了大量CPU,请考虑创建拥有(或试图找到现有的)类似混合的锁的实现。
答案 2 :(得分:1)
在Linux上,你应该最终不要自己实现锁,而是使用posix_rwlock_t
。
拥有4096
个这样的元素数组不应该出现任何特殊问题。 POSIX锁结构在Linux中非常有效地实现。特别是它们在可能的情况下在“快速路径”上使用原子操作,并且当该特定锁上存在拥塞时仅进入系统调用(特别是对于FUTEX)。因此,如果你相对仔细地实现任何线程一次只能拥有1个或2个锁,那么Linux上的约束只能由你的工作线程总数给出,而不是由对象本身的数量给出。