参考我之前关于不完整构造对象的问题,我有第二个问题.正如Jon Skeet指出的那样,在构造函数的末尾有一个隐式的内存障碍,可以确保final所有线程都可以看到这些字段.但是如果构造函数调用另一个构造函数呢?在每个人的最后是否有这样的记忆障碍,或者只是在第一个被召唤的人的最后?也就是说,当"错误"解决方案是:
public class ThisEscape {
public ThisEscape(EventSource source) {
source.registerListener(
new EventListener() {
public void onEvent(Event e) {
doSomething(e);
}
});
}
}
Run Code Online (Sandbox Code Playgroud)
正确的一个是工厂方法版本:
public class SafeListener {
private final EventListener listener;
private SafeListener() {
listener = new EventListener() {
public void onEvent(Event e) {
doSomething(e);
}
}
}
public static SafeListener newInstance(EventSource source) {
SafeListener safe = new SafeListener();
source.registerListener(safe.listener);
return safe;
}
}
Run Code Online (Sandbox Code Playgroud)
以下工作是否也会起作用?
public class MyListener {
private final EventListener listener;
private …Run Code Online (Sandbox Code Playgroud) 我最近读到了关于内存障碍和重新排序的问题,现在我对它有些困惑.
请考虑以下情形:
private object _object1 = null;
private object _object2 = null;
private bool _usingObject1 = false;
private object MyObject
{
get
{
if (_usingObject1)
{
return _object1;
}
else
{
return _object2;
}
}
set
{
if (_usingObject1)
{
_object1 = value;
}
else
{
_object2 = value;
}
}
}
private void Update()
{
_usingMethod1 = true;
SomeProperty = FooMethod();
//..
_usingMethod1 = false;
}
Run Code Online (Sandbox Code Playgroud)
在Update方法; 是_usingMethod1 = true获取或设置属性之前始终执行的语句?或者由于重新订购问题我们无法保证?
我们应该使用volatile像
private volatile …Run Code Online (Sandbox Code Playgroud)我阅读了"英特尔架构的英特尔优化指南指南".
但是,我仍然不知道何时应该使用
_mm_sfence()
_mm_lfence()
_mm_mfence()
Run Code Online (Sandbox Code Playgroud)
任何人都可以解释在编写多线程代码时何时应该使用它们?
首先,我知道这lock{}是合成糖Monitor.(哦,语法糖)
我正在玩简单的多线程问题,并发现无法完全理解锁定一些任意WORD的内存如何保护整个其他内存不被缓存的是寄存器/ CPU缓存等.使用代码示例来解释我所说的更容易:
for (int i = 0; i < 100 * 1000 * 1000; ++i) {
ms_Sum += 1;
}
Run Code Online (Sandbox Code Playgroud)
最终ms_Sum将包含100000000哪些当然是预期的.
现在我们年龄将执行相同的周期,但在2个不同的线程上,上限减半.
for (int i = 0; i < 50 * 1000 * 1000; ++i) {
ms_Sum += 1;
}
Run Code Online (Sandbox Code Playgroud)
由于没有同步,我们得到了不正确的结果 - 在我的4核机器上,它几乎是随机数52 388 219,略大于一半100 000 000.如果我们奉上ms_Sum += 1;的lock {},我们,事业,会得到完全正确的结果100 000 000.但是,什么是对我感兴趣的(真正的说,我是期待一样的行为),加入lock前,后ms_Sum += 1; …
简化问题:
与内存屏障相比,互锁操作引起的内存缓存一致性(或"刷新")的时间是否存在差异?让我们在C#中考虑 - 任何互锁操作与Thread.MemoryBarrier().我相信存在差异.
背景:
我读了很少关于内存障碍的信息 - 所有对预防特定类型的内存交互指令重新排序的影响,但是我找不到关于它们是否应该立即刷新读/写队列的一致信息.
实际上我发现很少有消息来源提到不能保证操作的即时性(只保证特定的重新排序是有保证的).例如
维基百科:"但是,要明确的是,这并不意味着任何操作都会在屏障完成时完成;只有完成操作的订购(当它们完成时)才能得到保证"
Freebsd.org(障碍是硬件特定的,所以我猜一个特定的操作系统并不重要):"内存屏障只是确定内存操作的相对顺序;它们不保证内存操作的时间"
另一方面,Interlocked操作 - 从他们的定义 - 导致立即刷新所有内存缓冲区以保证更新的最新值更新导致内存子系统用值锁定整个缓存行,以防止从任何访问(包括读取)其他CPU /核心,直到操作完成.
我纠正还是错了?
免责声明:
这是我在这里的原始问题的演变.在.NET中的可变新鲜度保证(易失性与易失性读取)
EDIT1: 修复了关于Interlocked操作的声明 - 内联文本.
编辑2: 完全删除演示代码+它的讨论(因为一些人抱怨太多的信息)
据我所知,C#是一种安全的语言,除了通过unsafe关键字之外,不允许访问未分配的内存.但是,当线程之间存在不同步的访问时,其内存模型允许重新排序.这会导致竞争危险,在实例完全初始化之前,对新实例的引用似乎可用于竞赛线程,并且是双重检查锁定的众所周知的问题.Chris Brumme(来自CLR团队)在他们的Memory Model文章中解释了这一点:
考虑标准的双锁协议:
if (a == null)
{
lock(obj)
{
if (a == null)
a = new A();
}
}
Run Code Online (Sandbox Code Playgroud)
这是在典型情况下避免锁定读取'a'的常用技术.它在X86上运行得很好.但它将被ECMA CLI规范的合法但薄弱的实施所打破.确实,根据ECMA规范,获取锁具有获取语义并释放锁具有释放语义.
但是,我们必须假设在建造'a'期间已经发生了一系列商店.这些商店可以任意重新排序,包括将它们推迟到将新对象分配给'a'的出版商店之后的可能性.那时,在store.release之前有一个小窗口离开锁.在该窗口内,其他CPU可以在引用'a'中导航并查看部分构造的实例.
我一直对"部分构造的实例"的含义感到困惑.假设.NET运行时在分配而不是垃圾收集(讨论)时清除内存,这是否意味着另一个线程可能会读取仍包含垃圾收集对象数据的内存(如不安全语言中发生的情况)?
考虑以下具体示例:
byte[] buffer = new byte[2];
Parallel.Invoke(
() => buffer = new byte[4],
() => Console.WriteLine(BitConverter.ToString(buffer)));
Run Code Online (Sandbox Code Playgroud)
以上是竞争条件; 输出将是00-00或00-00-00-00.但是,第二个线程是否有可能在数组的内存初始化为0 buffer 之前读取新引用,并输出一些其他任意字符串?
我想编写可移植代码(Intel、ARM、PowerPC...)来解决一个经典问题的变体:
Initially: X=Y=0
Thread A:
X=1
if(!Y){ do something }
Thread B:
Y=1
if(!X){ do something }
Run Code Online (Sandbox Code Playgroud)
其中目标是避免两个线程都在做的情况something。(如果两者都没有运行也没关系;这不是只运行一次的机制。)如果您发现我下面的推理中有一些缺陷,请纠正我。
我知道,我可以通过memory_order_seq_cstatomic stores 和loads实现目标,如下所示:
std::atomic<int> x{0},y{0};
void thread_a(){
x.store(1);
if(!y.load()) foo();
}
void thread_b(){
y.store(1);
if(!x.load()) bar();
}
Run Code Online (Sandbox Code Playgroud)
这实现了目标,因为
{x.store(1), y.store(1), y.load(), x.load()}事件必须有一些单一的总顺序,它必须与程序顺序“边缘”一致:
x.store(1) “在TO之前” y.load()y.store(1) “在TO之前” x.load()如果foo()被调用,那么我们有额外的优势:
y.load() “之前读取值” y.store(1)如果bar()被调用,那么我们有额外的优势:
x.load() “之前读取值” x.store(1)所有这些边组合在一起将形成一个循环:
x.store(1)“在TO之前”“在TO之前y.load()读取值” y.store(1)“在TO之前” x.load()“读取之前值”x.store(true)
这违反了订单没有周期的事实。
我故意使用非标准术语“在 TO …
在.NET中的lock关键字是语法糖各地Monitor.Enter及Monitor.Exit,所以你可以说,这段代码
lock(locker)
{
// Do something
}
Run Code Online (Sandbox Code Playgroud)
是相同的
Monitor.Enter(locker);
try
{
// Do Something
}
finally
{
Monitor.Exit(locker);
}
Run Code Online (Sandbox Code Playgroud)
但是,.NET框架还包括MemoryBarrier以类似方式工作的类
Thread.MemoryBarrier();
//Do something
Thread.MemoryBarrier();
Run Code Online (Sandbox Code Playgroud)
我很困惑,当我想用Thread.MemoryBarrier在lock/ Monitor版本?我更加困惑的是线程教程,它表明它们的功能相同.
据我所知,可见的区别是不需要锁定对象,我想使用Monitor你可以跨线程做一些事情MemoryBarrier在一个线程上.
我的直觉告诉我,另一个关键的区别是MemoryBarrier仅变量而不是方法.
最后这与现有问题无关何时在线程安全锁定代码中使用'volatile'或'Thread.MemoryBarrier()'?(C#),因为它专注于volatile我理解其使用的关键字.
我有一些不可变的数据结构,我想使用引用计数来管理,在SMP系统上的线程之间共享它们.
这是发布代码的样子:
void avocado_release(struct avocado *p)
{
if (atomic_dec(p->refcount) == 0) {
free(p->pit);
free(p->juicy_innards);
free(p);
}
}
Run Code Online (Sandbox Code Playgroud)
是否atomic_dec需要在它的内存屏障?如果是这样,什么样的记忆障碍?
附加说明:应用程序必须在PowerPC和x86上运行,因此欢迎任何特定于处理器的信息.我已经知道GCC原子内置.至于不变性,refcount是唯一在对象持续时间内发生变化的字段.
在的C++ 0x草案有围栏的概念似乎很明显,从围墙的CPU /芯片级的概念,或者说什么linux内核的家伙想到的围栏.问题在于草案是否真的意味着一个极其受限制的模型,或者措辞是否很差,它实际上意味着真正的围栏.
例如,在29.8 Fences下它表示如下:
如果存在原子操作X和Y,则释放围栏A与获取围栏B同步,两者都在某个原子对象M上操作,使得A在X之前被排序,X修改M,Y在B之前被排序,并且Y读取该值如果是释放操作,则由X写入或由假设释放序列中的任何一方写入的值X将结束.
它使用这些术语atomic operations和atomic object.草案中定义了这样的原子操作和方法,但它仅仅意味着那些吗?一个释放栅栏听起来像一个店围栏.在围栏之前不保证写入所有数据的商店围栏几乎是无用的.类似于装载(获取)围栏和完整围栏.
那么,C++ 0x中的栅栏/栅栏是否适当的栅栏和措辞是否非常差,或者它们是否如所描述的那样极其受限制/无用?
就C++而言,假设我有这个现有的代码(假设现在可以使用围栏作为高级构造 - 而不是在GCC中使用__sync_synchronize):
Thread A:
b = 9;
store_fence();
a = 5;
Thread B:
if( a == 5 )
{
load_fence();
c = b;
}
Run Code Online (Sandbox Code Playgroud)
假设a,b,c的大小在平台上具有原子拷贝.以上意味着c只会被分配9.注意我们并不关心线程B何时看到a==5,只是当它看到它时b==9.
C++ 0x中保证相同关系的代码是什么?
答案:如果您阅读我选择的答案和所有评论,您将获得情况的要点.C++ 0x似乎强制您使用带栅栏的原子,而普通硬件栅栏没有此要求.在许多情况下,只要sizeof(atomic<T>) == sizeof(T)和,这仍然可以用来代替并发算法atomic<T>.is_lock_free() == true.
不幸的是,这is_lock_free …
memory-barriers ×10
c# ×4
c++ ×3
.net ×2
atomic ×2
locking ×2
c ×1
c++11 ×1
constructor ×1
interlocked ×1
intrinsics ×1
java ×1
jvm ×1
memory-model ×1
monitor ×1
refcounting ×1
stdatomic ×1
x86 ×1