注意:我确实不是很擅长多线程编程,但是我当前的项目正在这样做,所以我试图弄清什么是线程安全的,什么不是线程安全的。
我正在阅读Eric Lippert 关于++ i所做的令人敬畏的答案之一。他说这是真正发生的事情:
这让我开始思考,如果两个线程在哪里调用++ i怎么办?如果第一个线程在步骤3上时第二个线程在步骤2上。(这意味着如果第二个线程在第一个线程将新值存储在变量中之前将值复制到临时位置,该怎么办?)
如果发生这种情况,那么似乎两个线程只会增加i一次,而不是两次。(除非整个事情都放在一个lock。中)。
i
lock
正如其他答案所指出的,不,++不是“线程安全的”。
当您了解多线程及其危害时,我认为将对您有所帮助,那就是开始非常精确地了解“线程安全”的含义,因为不同的人对其含义不同。从本质上讲,您在这里关注的线程安全方面是该操作是否为 原子 操作。“原子”操作可以保证在被另一个线程中断时不会中途完成。
(还有许多其他与原子性无关的线程问题,但仍可能属于某些人对线程安全性的定义。例如,给定两个线程每个变量的变量,而两个线程每个读取变量的线程,则是两个读者保证就其他两个线程发生突变的 顺序 达成一致吗?如果您的逻辑依赖于此,那么即使每次读取和写入都是原子操作,您也都很难解决线程安全问题。)
在C#中,几乎没有任何东西可以保证是原子的。简要地:
保证是原子的(有关详细信息,请参见规范。)
特别是, 不能 保证读写64位整数或浮点数是原子的。如果你说:
C.x = 0xDEADBEEF00000000;
在一个线程上,并且
C.x = 0x000000000BADF00D;
在另一个线程上,则可以在第三个线程上:
Console.WriteLine(C.x);
即使 逻辑上 该变量从不保存该值,也要写出0xDEADBEEF0BADF00D 。C#语言保留将写入写成等同于两个int的权利,并且实际上某些芯片确实以这种方式实现它。第一次写入后进行线程切换可能会导致读取器读取意外内容。
它的长短是:不要在没有锁定 任何东西的情况下 在两个线程之间共享 任何 东西。锁只有在满足时才会变慢。如果由于锁竞争 而导致 性能问题,请 修复导致锁竞争的任何体系结构缺陷 。如果锁没有竞争并且仍然太慢,则只有在这种情况下,您才应考虑使用危险的低锁技术。
当然,这里使用的常见的低锁技术是调用Threading.Interlocked.Increment,它以保证为原子的方式对整数进行递增。(但是请注意,它仍然无法保证类似的事情,如果两个线程在不同时间互锁两个不同变量的增量,而其他线程试图确定哪个增量是“第一个”发生的话。C#不能保证所有线程都可以看到一个一致的事件顺序。)
Threading.Interlocked.Increment