C++ 动态无锁内存分配器

C++ 动态无锁内存分配器,c++,c,linux,algorithm,lock-free,C++,C,Linux,Algorithm,Lock Free,编写满足无锁进度保证的算法或数据结构的困难之一是动态内存分配:调用malloc或new之类的东西不能保证以可移植的方式无锁。然而,存在许多无锁的malloc实现或新的无锁内存分配器,也有各种无锁内存分配器可用于实现无锁算法/数据结构 然而,我仍然不明白这实际上如何能够完全满足无锁进度保证,除非您专门将数据结构或算法限制在一些预先分配的静态内存池中。但是,如果您需要动态内存分配,我不明白任何所谓的无锁内存分配器从长远来看是如何真正实现无锁的。问题是,无论您的无锁malloc或new可能多么令人惊讶

编写满足无锁进度保证的算法或数据结构的困难之一是动态内存分配:调用
malloc
new
之类的东西不能保证以可移植的方式无锁。然而,存在许多无锁的malloc实现或新的无锁内存分配器,也有各种无锁内存分配器可用于实现无锁算法/数据结构

然而,我仍然不明白这实际上如何能够完全满足无锁进度保证,除非您专门将数据结构或算法限制在一些预先分配的静态内存池中。但是,如果您需要动态内存分配,我不明白任何所谓的无锁内存分配器从长远来看是如何真正实现无锁的。问题是,无论您的无锁
malloc
new
可能多么令人惊讶,最终您可能会耗尽内存,此时您必须求助于向操作系统请求更多内存。这意味着您最终必须调用
brk()
mmap()
或类似的低级等效程序才能访问更多内存。而且根本不能保证这些低级调用都是以无锁方式实现的


根本没有办法解决这个问题(除非您使用的是像MS-DOS这样的不提供内存保护的古老操作系统,或者您编写了自己的完全无锁操作系统——这两种情况既不实际也不可能。)那么,任何动态内存分配器怎么能真正做到无锁呢?

正如您所发现的,基本的OS分配器很可能不是无锁的,因为它必须处理多个进程和各种有趣的东西,这使得不引入某种锁非常困难

然而,在某些情况下,“无锁内存分配”并不意味着“从不锁定”,而是“统计上很少锁定,所以这并不重要”。这对于除最严格的实时系统以外的任何系统都适用。如果您的锁没有很高的争用性,那么锁或不锁实际上并不重要-无锁的目的实际上不是锁本身的开销,而是它成为瓶颈的难易程度,系统中的每个线程或进程都必须通过这一个地方来做任何有用的事情,它必须在队列中等待[它也可能不是真正的队列,它可能是“谁先醒来”或决定谁在当前调用方之后下一个出来的其他机制]

有几个不同的选项可以解决这个问题:

  • 如果您有一个有限大小的内存池,那么在软件启动时,您可以一次向操作系统请求所有内存。在内存从操作系统中分块后,它可以用作无锁池。明显的缺点是,它限制了可以分配的内存量。然后,您必须停止分配(使应用程序全部失败,或使特定操作失败)

    当然,在Linux或Windows这样的系统中,仍然不能保证在无锁情况下,内存分配意味着“对分配内存的即时访问”,因为系统可以并且将分配内存,而无需实际的物理内存备份,并且只有在实际使用内存时,物理内存页被分配给它。这可能涉及锁,例如磁盘I/O,以将其他页面分页到交换

  • 对于可能争夺锁的单个系统调用的时间“太多”的严格实时系统,解决方案当然是使用一个专用操作系统,一个在操作系统内有一个无锁分配器的操作系统(或者至少一个具有可接受的已知实时行为的操作系统,它最多锁定X微秒)[X可以小于1.0])。实时系统通常有一个内存池和固定大小的存储桶,用于回收旧分配,这可以以无锁方式完成-存储桶是一个链表,因此您可以使用原子比较和交换操作从该列表中插入/删除[可能是通过重试,因此尽管技术上它是无锁的,但在争用情况下它不是零等待时间]

  • 另一个可行的解决方案是拥有“每线程池”。如果在线程之间传递数据,这可能会变得有点复杂,但如果您接受“为重用而释放的内存可能最终会出现在不同的线程中”(这当然会导致一些问题)“所有内存现在都位于一个线程中,该线程从许多其他线程收集并释放信息,而所有其他线程的内存都已用完”)


你说得对,但是……由于分配器只从操作系统请求大数据块,不需要及时释放它们,因此对
sbrk()的调用总数
或任何可以严格限制到无关紧要的程度的东西。这当然不能满足硬实时要求,但我认为动态分配即使是完全无锁的也不能满足这些要求。你担心延迟,还是担心使用a?如果是后者,我认为最好操作系统使线程无法在内核中休眠,而持有的锁会阻止其他线程使用mmap。因此,就编写一个非阻塞算法而言,我认为使用
mmap
,从技术上讲并不是一个问题,因为只有当线程可能在持有mmap时休眠时,锁才是一个问题关键部分中的CPU缓存未命中可能是最严重的。在实际系统上运行的所有分配器都必须在某个时候耗尽内存;在分配静态池时耗尽的无锁分配器与在操作系统确定您已完成配置时耗尽的普通分配器没有本质区别