2017-09-22 64 views
1

满足无锁进程保证的编写算法或数据结构的困难之一是动态内存分配:调用类似mallocnew的调用不保证以便携方式无锁。然而,存在许多无锁实现mallocnew,并且还有各种可用于实现无锁算法/数据结构的无锁内存分配器。动态锁定内存分配器

但是,我仍然不明白如何实际上完全满足无锁进度保证,除非你特别限制你的数据结构或算法到一些预先分配的静态内存池。但是,如果你需要动态内存分配,我不明白如何任何指称的无锁内存分配器永远真正无锁的长期。问题是无论您的无锁mallocnew可能有多惊人,最终可能会导致内存不足,此时您必须要求操作系统提供更多内存。这意味着最终你必须调用brk()mmap()或者一些这样的低级别等价物,以实际获得更多的内存。并且不能保证任何这些低级调用都是以无锁方式实现的。 (除非您使用的是像MS-DOS这样的不提供内存保护的古老操作系统,或者您自己编写了完全无锁的操作系统 - 两种不实用的方案或者可能)。那么,怎样才能使任何动态内存分配器真正无锁?

+1

你说的没错,但是......由于分配只能从OS请求大块,并且不需要及时发布它们,对“sbrk()”或任何其他语言的调用总数可以严格限制到无关紧要的程度。当然,这并不能满足硬实时要求,但我认为即使动态分配完全无锁,也不会满足这些要求。 –

+0

您是否担心延迟,或者您是否担心使用https://en.wikipedia.org/wiki/Non-blocking_algorithm?如果是后者,我认为最好的操作系统使得线程无法在内核中睡眠,同时持有一个会阻止其他线程使用mmap的锁。所以就编写一个非阻塞算法而言,我认为使用'mmap'并不是一个问题,因为如果一个线程可以在保持一个线程的同时睡眠,那么锁只是一个问题。临界区内的一对CPU高速缓存未命中可能是你得到的最差的。 –

+0

在真实系统上运行的所有分配器在某个时刻不得不耗尽内存;在分配静态池时运行的无锁分配器在操作系统认为你已经拥有足够的时间的情况下与正常的分配器没什么质的区别。 –

回答

3

正如你发现自己一样,基本的操作系统分配器很可能不是无锁的,因为它必须处理多个进程和各种有趣的东西,这使得很难不引入某种锁。

但是,对于某些情况,“无锁内存分配”并不意味着“永不锁定”,而是“统计锁定非常少以至于无关紧要”。对于除最严格的实时系统以外的其他任何应用程序都适用。如果你的锁没有高度争用,那么锁或无锁并不重要 - 锁的目的不在于它本身的开销,而在于它容易成为瓶颈系统中的每个线程或进程都必须通过这个地方来做任何有用的事情,并且因为它这样做,所以它必须在队列中等待[它可能不是真正的队列,它可能是“谁先唤醒”或者一些其他机制,决定谁在下一个,当前呼叫者之后]。

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

  • 如果你有一个有限大小的存储器池,你可以问OS为所有存储一次,正在启动软件时。内存从操作系统分割出来之后,可以用作无锁池。明显的缺点是它可以分配多少内存。然后您必须停止分配(全部失败应用程序,或者失败该特定操作)。

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

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

  • 可以工作的另一个解决方案是拥有“每个线程池”。如果你在线程之间传递数据,这可能会变得复杂一些,但是如果你接受“为释放而释放的内存可能会以不同的线程结束”(这当然会导致出现问题,“现在所有的内存都在一个线程收集和释放许多其他线程的信息,所有其它线程都用完了内存“)