1

我只想知道为什么非阻塞并发比阻塞并发更好。在阻塞并发中你的线程必须等到其他线程完成它的执行。所以在这种情况下线程不会消耗 CPU。

但是,如果我谈论非阻塞并发,线程不会等待获得锁,如果某些线程包含锁,它们会立即返回。

例如在ConcurrentHashMap类中,put()方法内部有tryLock()一个循环。其他线程将处于活动状态并不断尝试检查锁是否已被释放,因为tryLock()它是非阻塞的。我假设在这种情况下,不需要使用 CPU。

那么暂停线程直到其他线程完成执行并在工作完成时唤醒线程不是很好吗?

4

2 回答 2

2

阻塞或非阻塞并发是否更好取决于您期望等待多长时间才能获取您正在等待的资源。

通过阻塞等待(即 C 语言中的互斥锁),操作系统内核使等待线程进入睡眠状态。所需资源可用之前,CPU 调度程序不会为其分配任何时间。这里的优点是,正如你所说,这个线程在睡眠时不会消耗任何 CPU 资源。

但是,有一个缺点:使线程进入睡眠状态、确定何时唤醒以及再次唤醒它的过程既复杂又昂贵,并且可能会抵消线程在等待时不消耗 CPU 所带来的节省。此外(可能正因为如此),一旦资源可用,操作系统可能会选择立即唤醒线程,因此锁定的等待时间可能会超过必要的时间。

非阻塞等待(也称为自旋锁)在等待时确实会消耗 CPU 资源,但可以节省让线程进入睡眠状态、确定何时应该唤醒它以及唤醒它的开销。一旦锁变得空闲,它也可能能够更快地响应,因为它在何时可以继续执行方面不太受操作系统的影响。

因此,作为一个非常普遍的规则,如果您希望只等待很短的时间(例如,另一个线程完成一个条目可能需要几个 CPU 周期),您应该更喜欢自旋锁ConcurrentHashMap。对于更长的等待(例如,在同步 I/O 上,或在单个复杂计算上等待的多个线程),互斥锁(阻塞等待)可能更可取。

于 2018-06-06T10:05:34.450 回答
0

如果您以 ConcurrentHashMap 为例,考虑到由于多个线程执行更新操作(如 put)而导致的开销,并阻塞等待锁释放(正如您提到的其他线程将处于活动状态并不断尝试检查锁是否已被释放),情况并非总是如此。

与 HashTable 相比,ConcurrentHashMap 中的并发控制是分开的。所以多个线程可以获取锁(在表的段上)。

最初,ConcurrentHashMap 类支持硬连线预设并发级别 32。这允许最多 32 个 put 和/或 remove 操作同时进行(当超过 32 个线程同时尝试更新时,同步以外的因素往往成为瓶颈。)

此外,使用 get(key) 和 containsKey(key) 成功检索(当密钥存在时)通常无需锁定即可运行。

例如,一个线程可能正在添加一个元素,这种锁定策略无法完成的操作是仅在元素不存在时才添加元素(ConcurrentReaderHashMap 提供了这样的功能)。

此外,size() 和 isEmpty() 方法需要跨 32 个控制段进行累积,因此可能会稍微慢一些。

于 2018-06-06T11:06:12.967 回答