对于简单的计数器(例如计数器),如果有多个线程将增加数量。我读到互斥锁会降低效率,因为线程必须等待。因此,对我来说,原子计数器将是最有效的,但是我从内部了解到它基本上是锁吗?所以我想我很困惑,哪一个比另一个更有效。
Answers:
如果您有一个支持原子操作的计数器,它将比互斥锁更有效。
从技术上讲,原子将在大多数平台上锁定内存总线。但是,有两个改善的细节:
最小(符合标准)互斥量实现需要2个基本要素:
由于C ++标准要求“同步”关系,因此没有比这更简单的方法了。
最小(正确)的实现可能看起来像这样:
class mutex {
std::atomic<bool> flag{false};
public:
void lock()
{
while (flag.exchange(true, std::memory_order_relaxed));
std::atomic_thread_fence(std::memory_order_acquire);
}
void unlock()
{
std::atomic_thread_fence(std::memory_order_release);
flag.store(false, std::memory_order_relaxed);
}
};
由于其简单性(它无法挂起执行线程),因此在低竞争情况下,此实现可能胜过a std::mutex
。但是即使那样,仍然很容易看到受此互斥锁保护的每个整数增量都需要执行以下操作:
atomic
发行互斥量商店atomic
比较并交换(读-修改-写)来获取该互斥(可能多次)如果将其与以std::atomic<int>
单个(无条件)读-修改-写(例如fetch_add
)递增的独立变量进行比较,则可以合理预期,原子操作(使用相同的排序模型)将优于互斥量为用过的。
原子整数是一个用户模式对象,因为它比在内核模式下运行的互斥锁效率更高。原子整数的范围是单个应用程序,而互斥锁的范围是计算机上所有正在运行的软件的范围。
Java中的原子变量类能够利用处理器提供的比较和交换指令。
这是差异的详细描述:http : //www.ibm.com/developerworks/library/j-jtp11234/
大多数处理器都支持原子读取或写入,并且通常支持原子cmp&swap。这意味着处理器本身在单个操作中写入或读取最新值,并且与普通的整数访问相比,可能会损失一些周期,尤其是因为编译器无法围绕原子操作优化到几乎与正常情况一样。
另一方面,互斥锁是要进入和离开的许多代码行,并且在执行期间,访问同一位置的其他处理器完全停滞了,因此显然它们的开销很大。在未经优化的高级代码中,互斥锁进入/退出和原子将是函数调用,但是对于互斥锁,在您的互斥锁进入函数返回以及启动退出函数时,任何竞争的处理器都将被锁定。对于atomic,只有实际操作的持续时间被锁定。优化应该降低成本,但不是全部。
如果您尝试递增,那么您的现代处理器可能支持原子递增/递减,这将非常有用。
如果没有,则可以使用处理器原子cmp&swap或使用互斥锁来实现。
互斥体:
get the lock
read
increment
write
release the lock
原子cmp和交换:
atomic read the value
calc the increment
do{
atomic cmpswap value, increment
recalc the increment
}while the cmp&swap did not see the expected value
因此,第二个版本有一个循环(如果另一个处理器在我们的原子操作之间增加值,因此值不再匹配,并且增加将是错误的),可能会变长(如果有很多竞争者),但通常应该比互斥锁版本,但互斥锁版本可能允许该处理器切换任务。
Mutex
是内核级语义,即使在 Process level
。请注意,这有助于跨进程边界而不是仅在进程内部(对于线程)扩展互斥。比较贵。
原子计数器 AtomicInteger
例如,基于CAS,通常尝试尝试执行操作直到成功。基本上,在这种情况下,线程竞争或竞争以原子方式递增/递减值。在这里,您可能会看到一个试图使用当前值的线程正在使用良好的CPU周期。
由于您要维护计数器,因此AtomicInteger \ AtomicLong将是最适合您的用例的。