Vac*_*ano 56 c# multithreading
注意:我真的不是很擅长多线程编程,但我目前的项目让我这样做,所以我试图了解线程安全和什么不是.
我正在阅读Eric Lippert 关于++我所做的那些令人敬畏的答案之一.他说这才是真正发生的事情:
这让我思考,如果调用++ i的两个线程怎么办?如果第一个线程在步骤3,当第二个线程在步骤2时.(这意味着如果第二个线程在第一个线程将新值存储在变量中之前将值复制到临时位置,该怎么办?)
如果发生这种情况,那么两个线程似乎只会增加i
一次而不是两次.(除非整件事情都在lock
.)
Eri*_*ert 77
正如其他答案所指出的那样,不,++不是"线程安全".
当你了解多线程及其危害时,我认为有助于你开始非常精确地理解你所谓的"线程安全",因为不同的人通过它来表达不同的东西.基本上,您在此关注的线程安全方面是操作是否是原子操作."原子"操作是在被另一个线程中断时保证不会完成一半的操作.
(还有很多其他的线程问题与原子性无关,但可能仍然属于某些人对线程安全的定义.例如,给定两个线程,每个变量变量,两个线程每个读取变量,是两个读者保证同意其他两个线程发生突变的顺序?如果你的逻辑依赖于那个,那么即使每个读写都是原子的,你也有一个非常困难的线程安全问题需要处理.)
在C#中,几乎没有任何东西可以保证是原子的.简述:
保证是原子的(详见具体细节.)
特别是,读取和写入64位整数或浮点数并不保证是原子的.如果你说:
C.x = 0xDEADBEEF00000000;
Run Code Online (Sandbox Code Playgroud)
在一个线程上,和
C.x = 0x000000000BADF00D;
Run Code Online (Sandbox Code Playgroud)
在另一个线程上,然后可以在第三个线程上:
Console.WriteLine(C.x);
Run Code Online (Sandbox Code Playgroud)
有写出0xDEADBEEF0BADF00D,即使逻辑上变量从未保持该值.C#语言保留写入相当于写入两个整数的长期写入的权利,一个接一个,实际上一些芯片确实以这种方式实现.第一次写入后的线程切换可能会导致读取器读取意外的内容.
它的长短是:不要在没有锁定东西的情况下在两个线程之间共享任何东西.锁定只有在满足时才会变慢; 如果由于争用锁而出现性能问题,则修复导致争用锁的任何架构缺陷.如果锁没有争用且仍然太慢,那么只有你应该考虑使用危险的低锁技术.
这里使用的常见低锁技术当然是调用Threading.Interlocked.Increment
,它以保证原子的方式执行整数的增量.(但请注意,如果两个线程在不同时间分别执行两个不同变量的互锁增量,并且其他线程正在尝试确定哪个增量"首先"发生,那么它仍然无法保证会发生什么.C#不保证所有线程都可以看到单个一致的事件排序.)
Ode*_*ded 32
不,不是.
您提出的分析非常正确 - 如果++
(和--
)运算符没有使用适当的锁定语义,则它们容易受到竞争条件的影响.
这些很难做到,但幸运的是,BCL为这些特定情况提供了现成的解决方案:
Interlocked.Increment
如果您想要原子增量操作,则应该使用.还有一个Decrement
和定义的几个其他有用的原子操作Interlocked
类.
增加指定的变量并将结果存储为原子操作.
不,i++
看起来很紧凑,但它确实只是简写i = i + 1
,在这种形式下,更容易看到它涉及读取和写入'i'.没有锁定它根本不是线程安全的.
另外两个论点是:
Interlocked.Increment (ref int x)
线程安全是罕见且昂贵的,它将在可用时明确地公布.