在x86处理器下,我不确定比较和交换原子操作和加载链接/存储条件操作之间的区别.后者比前者更安全吗?情况是第一个比第二个好吗?
我正在使用OpenMP,需要使用fetch-and-add操作.但是,OpenMP不提供适当的指令/调用.我想保留最大的可移植性,因此我不想依赖编译器内在函数.
相反,我正在寻找一种方法来利用OpenMP的原子操作来实现这一点,但我已经走到了尽头.甚至可以这样做吗?注意,以下代码几乎可以满足我的需求:
#pragma omp atomic
x += a
Run Code Online (Sandbox Code Playgroud)
几乎 - 但不完全,因为我真的需要旧的价值x.fetch_and_add应定义为产生与以下相同的结果(仅非锁定):
template <typename T>
T fetch_and_add(volatile T& value, T increment) {
T old;
#pragma omp critical
{
old = value;
value += increment;
}
return old;
}
Run Code Online (Sandbox Code Playgroud)
(如果我没有弄错的话,可以要求一个等价的问题进行比较和交换,但可以用另一个来实现.)
我正在尝试编写一个只能调用一次的线程安全方法(每个对象实例).如果之前已调用异常,则应抛出异常.
我想出了两个解决方案.他们都是正确的吗?如果没有,他们有什么问题?
用lock:
public void Foo()
{
lock (fooLock)
{
if (fooCalled) throw new InvalidOperationException();
fooCalled = true;
}
…
}
private object fooLock = new object();
private bool fooCalled;
Run Code Online (Sandbox Code Playgroud)public void Foo()
{
if (Interlocked.CompareExchange(ref fooCalled, 1, 0) == 1)
throw new InvalidOperationException();
…
}
private int fooCalled;
Run Code Online (Sandbox Code Playgroud)
如果我没有弄错的话,这个解决方案具有无锁的优点(在我的情况下似乎无关紧要),并且它需要更少的私有字段.
我也愿意接受合理的意见,哪些解决方案应该是首选的,并且如果有更好的方法可以提出进一步的建议.
而与之相比
当块为空时,while和do-while在功能上是等效的,尽管看起来更自然:
do {} while (keepLooping());
while (keepLooping()) {}
Run Code Online (Sandbox Code Playgroud)
使用空块的while/do-while的一个典型用例是使用compareAndSet(CAS)强制更新原子对象.例如,下面的代码将以a线程安全的方式递增:
int i;
AtomicInteger a = new AtomicInteger();
while (!a.compareAndSet(i = a.get(), i + 1)) {}
Run Code Online (Sandbox Code Playgroud)
上下文
java.util.concurrent的几个部分使用do {} while (...)CAS操作的惯用语和ForkJoinPool解释的javadoc :
有几种异常
do {} while (!cas...)现象是强制更新CAS变量的最简单方法.
既然他们承认这是不寻常的,我认为他们意味着最好而不是最简单.
题
是否存在do {} while (!cas)比while (!cas) {}什么原因更有效的情况?
以下是基于的互锁方法的实现Interlocked.CompareExchange。
这段代码SpinWait在重复之前是否宜使用自旋?
public static bool AddIfLessThan(ref int location, int value, int comparison)
{
int currentValue;
do
{
currentValue = location; // Read the current value
if (currentValue >= comparison) return false; // If "less than comparison" is NOT satisfied, return false
}
// Set to currentValue+value, iff still on currentValue; reiterate if not assigned
while (Interlocked.CompareExchange(ref location, currentValue + value, currentValue) != currentValue);
return true; // Assigned, so return true
}
Run Code Online (Sandbox Code Playgroud)
我已经看到SpinWait在这种情况下使用过,但是我的理论是它应该是不必要的。毕竟,循环仅包含少量指令,并且总是有一个线程在进行中。
假设有两个线程竞相执行此方法,并且第一个线程立即成功执行,而第二个线程最初不做任何更改,必须重申。没有其他竞争者,第二个线程是否有可能在第二次尝试时失败 …
Java代码:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
public class SmallerCASTest {
public static void main(String[] args){
final long MAX = 500l * 1000l * 1000l;
final AtomicLong counter = new AtomicLong(0);
long start = System.nanoTime();
while (true) {
if (counter.incrementAndGet() >= MAX) {
break;
}
}
long casTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
System.out.println("Time Taken=" + casTime + "ms");
}
}
Run Code Online (Sandbox Code Playgroud)
C代码:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define NITER 500000000
int main (){
long val = …Run Code Online (Sandbox Code Playgroud) 假设一些"N"个线程正在尝试CAS一个AtomicInteger变量,是否保证CAS必须成功完成一个线程?
是否有可能所有"N"线程都尝试失败?
Java提供 AtomicInteger,AtomicLong等等,基本上编译成 CAS指令在硬件层面.但是,为什么这样AtomicXXX类不存在其他原始类型,如short和浮点数喜欢float和double?
根据这个问题的答案,似乎x86上的LOCK CMPXCHG实际上会导致完全屏障.据推测,这也是Unsafe.compareAndSwapInt()引擎盖下产生的.我很难理解为什么会这样:使用MESI协议,在更新缓存行之后,CPU是否只会使其他内核上的缓存行无效,而不是耗尽执行CAS的核心的所有存储/加载缓冲区?似乎对我很浪费......
在研究 AtomicInteger 时,我发现这个 API 提供了两种方法。
比较和交换:
如果当前值(称为见证值)==预期值,则以原子方式将该值设置为 newValue,并具有由以下指定的记忆效应
VarHandle.compareAndExchange(java.lang.Object...)
比较并设置:
以原子方式将该值设置为
newValue当前值value == expectedValue,并具有由 指定的记忆效应VarHandle.compareAndSet(java.lang.Object...)。
我无法理解两者之间的区别,请帮助提供合适的例子。
compare-and-swap ×10
java ×6
atomic ×5
c# ×2
c++ ×2
concurrency ×2
interlocked ×2
.net ×1
c ×1
do-while ×1
java-9 ×1
openmp ×1
performance ×1
spinwait ×1
while-loop ×1