我继续运行使用双重检查锁定的代码,我仍然对它为什么会被使用感到困惑.
我最初不知道双重检查锁定是否被打破,当我学会它时,它为我放大了这个问题:为什么人们首先使用它?是不是比较和交换更好?
if (field == null)
Interlocked.CompareExchange(ref field, newValue, null);
return field;
Run Code Online (Sandbox Code Playgroud)
(我的问题同时适用于C#和Java,尽管上面的代码是针对C#的.)
与原子操作相比,双重检查锁定是否具有某种固有优势?
c# java synchronization compare-and-swap double-checked-locking
简介:我曾预料到,只要加载的值很少改变std::atomic<int*>::load,std::memory_order_relaxed就会接近直接加载指针的性能.我看到原子负载的性能远远低于Visual Studio C++ 2012上的正常负载,因此我决定进行调查.事实证明原子负载是作为比较和交换循环实现的,我怀疑它不是最快的实现.
问题:是否有某些原因std::atomic<int*>::load需要进行比较和交换循环?
背景:我相信MSVC++ 2012正在基于此测试程序对指针的原子加载进行比较和交换循环:
#include <atomic>
#include <iostream>
template<class T>
__declspec(noinline) T loadRelaxed(const std::atomic<T>& t) {
return t.load(std::memory_order_relaxed);
}
int main() {
int i = 42;
char c = 42;
std::atomic<int*> ptr(&i);
std::atomic<int> integer;
std::atomic<char> character;
std::cout
<< *loadRelaxed(ptr) << ' '
<< loadRelaxed(integer) << ' '
<< loadRelaxed(character) << std::endl;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我正在使用一个__declspec(noinline)函数来隔离与原子负载相关的汇编指令.我做了一个新的MSVC++ 2012项目,添加了一个x64平台,选择了发布配置,在调试器中运行程序并查看了反汇编.事实证明,两者std::atomic<char>和std::atomic<int>参数最终会给出相同的调用loadRelaxed<int>- 这必须是优化器所做的事情.这是被调用的两个loadRelaxed实例的反汇编:
loadRelaxed<int …
在.NET框架中,原子操作CompareAndExchange仅对定义int,long,double和引用类型.但我需要CompareAndExchange bool类型.我怎样才能实现CompareAndSwap的bool?
以下两个引用似乎矛盾:
https://www.kernel.org/doc/Documentation/atomic_ops.txt
int atomic_cmpxchg(atomic_t * v,int old,int new);
这会对具有给定的旧值和新值的原子值v执行原子比较交换操作。像所有atomic_xxx操作一样,只要通过atomic_xxx操作执行* v的所有其他访问,atomic_cmpxchg将仅满足其原子性语义。
atomic_cmpxchg需要在操作周围使用显式的内存屏障。
与
https://www.kernel.org/doc/Documentation/memory-barriers.txt
任何修改内存中某些状态并返回状态信息(旧的或新的)的原子操作都意味着在实际操作的每一端都存在一个SMP条件的通用内存屏障(smp_mb())(除了显式锁定操作,已描述)后来)。这些包括:
Run Code Online (Sandbox Code Playgroud)<...> atomic_xchg(); atomic_cmpxchg(); <...>这些用于实现LOCK类和UNLOCK类操作以及针对对象破坏调整参考计数器的事情,因此隐式内存屏障效应是必需的。
因此,应该atomic_xchg()手动消除内存障碍吗?
multithreading synchronization atomic linux-kernel compare-and-swap
我知道,MESI协议成功地保证了不同内核的内存(缓存)视图相同。我的问题来自这样一个事实,即在写入过程中,MESI保证高速缓存由CPU独占,然后原子CMPXCHG只是原子地比较和交换值。那么,当我们已经有了MESI协议的保证时,为什么还要使用LOCK指令来锁定高速缓存行呢?
据我了解,synchronized关键字将本地线程缓存与主内存同步。volatile 关键字基本上总是在每次访问时从主内存中读取变量。当然,访问主内存比本地线程缓存要昂贵得多,因此这些操作的成本很高。然而,CAS 操作使用低级硬件操作,但仍然必须访问主存储器。那么 CAS 操作如何更快呢?
C++ 标准说,原子上的 RMW(读-修改-写)操作将对原子变量的最新值进行操作。因此memory_order_relaxed,当从多个线程并发执行时,使用这些操作不会影响 RMW 操作。
我假设只有当 RMW 操作存在一些内存屏障或栅栏时,即使指定的内存顺序是“放松的”,这种行为也是可能的。如果我的理解有误,请纠正我,并解释如果不使用此类内存屏障,这些操作如何处理最新值。如果我的理解是正确的,那么我是否可以进一步假设使用 Acquire-Release 或 Seq-CST 内存顺序不应该对 RMW 操作产生额外的性能影响,比如 ARM 或 Alpha 等弱排序架构。提前致谢。
在基于 CAS 的循环中,例如下面的循环,使用暂停对 x86 有益吗?
void atomicLeftShift(atomic<int>& var, int shiftBy)
{
While(true) {
int oldVal = var;
int newVal = oldVal << shiftBy;
if(var.compare_exchange_weak(oldVal, newVal));
break;
else
_mm_pause();
}
}
Run Code Online (Sandbox Code Playgroud) 似乎要使用 CMPXCHG16B,必须定义 _STD_ATOMIC_ALWAYS_USE_CMPXCHG16B = 1 以便使用这些指令。
为什么这是默认设置?除非我阅读整个 atomic.h 标头,否则我永远不会发现这一点。
STL 中还有哪些其他全局定义?是否有一份清单可供审查,以便人们能够可靠地了解这些实施细节?
我读过https://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange
以原子方式将 *this 的对象表示形式 (C++20 之前) 值表示形式 (C++20 起) 与预期的表示形式进行比较,如果它们按位相等,则将前者替换为所需的(执行读取-修改-写入操作) )。否则,将 *this 中存储的实际值加载到预期中(执行加载操作)。
据我了解,代码如下
bool expected=true;
extern atomic<bool> b = false;
void foo ()
{
//
while(!b.compare_exchange_weak(expected, false));
//
}
Run Code Online (Sandbox Code Playgroud)
循环运行一次后(忽略虚假失败)它将失败,并将写入预期false,因此在第二次迭代时,compare_exchange_weak将返回成功,尽管b尚未更改为true。但这一切有什么意义呢?我虽然可以用它作为同步锁,等待其他线程更改b,但现在我想不出它的用法。
cppreference 中的示例还表明,两次调用compare_exchange_strong 后将会成功。
#include <atomic>
#include <iostream>
std::atomic<int> ai;
int tst_val= 4;
int new_val= 5;
bool exchanged= false;
void valsout()
{
std::cout << "ai= " << ai
<< " tst_val= " << tst_val
<< " new_val= " << new_val
<< " exchanged= …Run Code Online (Sandbox Code Playgroud) compare-and-swap ×10
atomic ×6
c++ ×5
stdatomic ×3
c# ×2
visual-c++ ×2
x86 ×2
boolean ×1
c++11 ×1
java ×1
linux-kernel ×1
memory-model ×1
mesi ×1
x86-64 ×1