2012-12-13 94 views
3

我总是被告知要将锁锁定在多个线程可以访问的变量上,我一直认为这是因为您想确保在使用之前写入的值不会发生变化 即螺纹为什么危险?

mutex.lock() 
int a = sharedVar 
a = someComplexOperation(a) 
sharedVar = a 
mutex.unlock() 

而且这是有道理的,你会锁定。但在其他情况下,我不明白为什么我不能逃避不使用Mutexes。

线程A:

sharedVar = someFunction() 

线程B:

localVar = sharedVar 

什么可能出问题在这种情况下?特别是如果我不在乎线程B读取线程A分配的任何特定值。

+0

我不是一个权威人士,但我猜测这是因为你最终可能会碰撞。如果您在写入时尝试访问变量,那么您可能会遇到访问冲突。 –

+0

@ Pow-Ian内存访问始终是可串行化的。硬件负责以合理的方式处理这个问题。 –

+0

问题是一个变量可能包含一个你不期待的值。当你只读时,这不是问题。当你开始写和使用变量时,你不能再根据变量的值做出正确的决定。 – dmaij

回答

5

这取决于您使用的语言,任何框架和平台的类型sharedVar。在很多情况下,将单个值分配给sharedVar可能需要多个指令,在这种情况下,您可能会读取值的“半集”副本。

即使情况并非如此,分配也是原子分配,但如果没有memory barrier,您可能看不到最新值。

+0

这不只是类型。它也依赖于语言(有些只有引用类型,其中赋值是近似全宇称的)和内存模型(可以保证某些或所有类型的原子性)。 – delnan

+0

@delnan是的 - 类型/语言/框架/底层硬件/等 - 这一切都很重要。 –

+0

了解观察部分更新值的术语是有用的:*撕裂*。在尝试哄骗硬件以自动执行更新时,对齐非常重要。 –

0

这可能会出错,因为线程调度程序可能会挂起和恢复线程,所以您无法确定这些指令的执行顺序。这或许也同样是这个顺序:

线程B:

localVar = sharedVar 

线程A:

sharedVar = someFunction() 

在这种情况下,localvar将是无效或0(或者在某些completeley意外的值一种不安全的语言),可能不是你想要的。

互斥锁实际上不会解决这个问题。您提供的示例并不适合并行化。

1

主要问题是赋值运算符(运算符=在C++中)并不总是保证为原子的(即使对于基元,内置类型也不是这样)。用简单的英语,这意味着分配可能需要超过一个时钟周期才能完成。如果在该过程中线程被中断,则该变量的当前值可能被破坏。

让我建立了你的例子:

比方说sharedVar是一些物体operator=定义为这样:

object& operator=(const object& other) { 
    ready = false; 
    doStuff(other); 
    if (other.value == true) { 
     value = true; 
     doOtherStuff(); 
    } else { 
     value = false; 
    } 
    ready = true; 
    return *this; 
} 

如果从你的例子线程A在这个函数的中途中断,线程B开始运行时就绪仍然是假的。这可能意味着当线程B试图将其复制到局部变量中时,该对象只能被部分复制,或处于某种中间无效状态。

对于这个特别讨厌的例子,想象一下删除节点被删除的数据结构,然后在它被设置为NULL之前中断。

(有关不需要锁(又名结构的一些更多的信息,是原子),here是另外一个问题,讨论多一点有关。)

+0

即使在简单类型的情况下,这是否也是一个问题? (如果共享var是一个浮点数) – Whyrusleeping

+0

对于一些简单的类型,是的,这仍然是一个问题。 C++ 11引入了std :: atomic <>包装类,它将为许多基本类型提供有保证的原子访问,但大多数情况下,你不能总是假设看起来是原子的东西将是原子的,除非编译器保证就是这样。 [Here](http://www.informit.com/guides/content.aspx?g=cplusplus&seqNum=469)是我发现的一篇文章,讲述了一些在MSVC下是或不是原子的操作符,但是主要的收获是基本整数数学通常是原子的,但其他的东西可能不是。 –

+0

[这里](http://stackoverflow.com/questions/1292786/is-updating-double-operation-atomic)是另一个似乎有更详细的信息,具体有关浮点类型的问题。 –

3

MSDN杂志有不同的很好的解释问题您可以在多线程代码中遇到的问题:

  • 忘记同步
  • 不正确的粒度
  • 读取和写入撕裂
  • 无锁的重新排序
  • 锁定护航
  • 两步舞
  • 优先级反转

在你的问题中的代码是特别容易读/写撕裂。但是,既没有锁也没有内存障碍的代码也受到无锁定重新排序(其可能包括推测性写入,其中线程B读取线程A从未存储的值),其中副作用对于第二秒可见线程的顺序与它们在源代码中的显示顺序不同。

它继续描述一些已知的设计模式,它避免了这些问题:

  • 不变性
  • 纯度
  • 隔离

的文章可以here

+0

感谢您阅读本文! – Whyrusleeping

+0

@BenVoigt是否可以刷新文章链接? –