++ 运算符线程安全吗? [英] Is the ++ operator thread safe?

查看:33
本文介绍了++ 运算符线程安全吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

注意:我真的不太擅长多线程编程,但是我当前的项目让我这样做,所以我试图弄清楚什么是线程安全的,什么不是.>

我正在阅读 Eric Lippert 的 关于 ++i 做什么的很棒的答案.他说这就是真正发生的事情:

  1. x 被评估以产生变量
  2. 将变量的值复制到临时位置
  3. 临时值递增以产生新值(不覆盖临时值!)
  4. 新值存储在变量中
  5. 运算的结果是新值

这让我开始思考,如果两个线程调用 ++i 会怎样?如果第一个线程在第 3 步,而第二个线程在第 2 步.(意味着如果在第一个线程将新值存储在变量中之前,第二个线程将值复制到临时位置会怎样?)

如果发生这种情况,那么看起来两个线程只会增加 i 一次而不是两次.(除非整个事情都在 lock 中.)

解决方案

正如其他答案所指出的,不,++ 不是线程安全的".

当您了解多线程及其危害时,我认为会有所帮助的事情是开始非常精确地理解线程安全"的含义,因为不同的人对它的含义不同.本质上,您在这里关心的线程安全方面是操作是否是原子.原子"操作是保证在被另一个线程中断时不会中途完成的操作.

(还有很多其他线程问题与原子性无关,但可能仍属于某些人对线程安全的定义.例如,给定两个线程每个改变一个变量,两个线程每个读取变量,两个读者是否保证就其他两个线程进行更改的顺序达成一致?如果您的逻辑依赖于此,那么即使每次读取和写是原子的.)

在 C# 中,几乎没有任何东西可以保证是原子的.简而言之:

  • 读取 32 位整数或浮点数
  • 阅读参考资料
  • 写入 32 位整数或浮点数
  • 写参考

保证是原子的(具体细节见规范.)

特别是,读取和写入 64 位整数或浮点数不能保证是原子的.如果你说:

C.x = 0xDEADBEEF00000000;

在一个线程上,以及

C.x = 0x000000000BADF00D;

在另一个线程上,然后可以在第三个线程上:

Console.WriteLine(C.x);

写出 0xDEADBEEF0BADF00D,即使逻辑上变量从未保持该值.C# 语言保留将 long 写入等效于写入两个 int 的权利,一个接一个,实际上一些芯片确实以这种方式实现它.第一次写入后的线程切换可能会导致读者读取意外内容.

总而言之:不要在没有锁定某些东西的情况下在两个线程之间共享任何东西.锁只有在满足时才会慢;如果由于争用锁而导致性能问题,那么修复导致争用锁的任何架构缺陷.如果锁没有争用并且仍然太慢,那么你才应该考虑使用危险的低锁技术.

此处使用的常见低锁技术当然是调用 Threading.Interlocked.Increment,它以保证原子性的方式执行整数的增量.(但是请注意,它仍然不能保证如果两个线程各自在不同时间执行两个不同变量的互锁增量,并且其他线程试图确定哪个增量首先"发生.C# 不保证所有线程都可以看到一个一致的事件顺序.)

NOTE: I am really not very good at Multithreaded programming, but my current project has me doing it so I am trying to get my head around what is thread safe and what is not.

I was reading one of Eric Lippert's awesome answers on what ++i does. He says this is what really happens:

  1. x is evaluated to produce the variable
  2. the value of the variable is copied to a temporary location
  3. the temporary value is incremented to produce a new value (not overwriting the temporary!)
  4. the new value is stored in the variable
  5. the result of the operation is the new value

This got me to thinking, what if two threads where calling ++i? If the first thread is at step 3 when the second thread is on step 2. (Meaning what if the second thread copies the value off to the temp location before the first thread stores the new value in the variable?)

If that happens then it would seem that both threads would only increment i once instead of twice. (Unless the whole thing is in a lock.)

解决方案

As other answers have pointed out, no, ++ is not "threadsafe".

Something that I think will help as you learn about multithreading and its hazards is to start being very precise about what you mean by "threadsafe", because different people mean different things by it. Essentially the aspect of thread safety you are concerned about here is whether the operation is atomic or not. An "atomic" operation is one which is guaranteed to not be halfway complete when it is interrupted by another thread.

(There are plenty of other threading problems that have nothing to do with atomicity but which may still fall under some people's definitions of thread safety. For example, given two threads each mutating a variable, and two threads each reading the variable, are the two readers guaranteed to agree on the order in which the other two threads made mutations? If your logic depends on that, then you have a very difficult thread safety problem to deal with even if every read and write is atomic.)

In C#, practically nothing is guaranteed to be atomic. Briefly:

  • reading a 32 bit integer or float
  • reading a reference
  • writing a 32 bit integer or float
  • writing a reference

are guaranteed to be atomic (see the specification for the exact details.)

In particular, reading and writing a 64 bit integer or float is not guaranteed to be atomic. If you say:

C.x = 0xDEADBEEF00000000;

on one thread, and

C.x = 0x000000000BADF00D;

on another thread, then it is possible to on a third thread:

Console.WriteLine(C.x);

have that write out 0xDEADBEEF0BADF00D, even though logically the variable never held that value. The C# language reserves the right to make writing to a long equivalent to writing to two ints, one after the other, and in practice some chips do implement it that way. A thread switch after the first of the writes can cause a reader to read something unexpected.

The long and short of it is: do not share anything between two threads without locking something. Locks are only slow when they are contented; if you have a performance problem because of contended locks then fix whatever architectural flaw is leading to contended locks. If the locks are not contended and are still too slow, only then should you consider going to dangerous low-lock techniques.

The common low-lock technique to use here is of course to call Threading.Interlocked.Increment, which does an increment of an integer in a manner guaranteed to be atomic. (Note however that it still does not make guarantees about things like what happens if two threads are each doing interlocked increments of two different variables at different times, and other threads are trying to determine which increment happened "first". C# does not guarantee that a single consistent ordering of events is seen by all threads.)

这篇关于++ 运算符线程安全吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆