1)竞争
两个线程同时修改一个变量,后一个人的修改会覆盖前一个人的修改,这种情况叫做竞争。
2)死锁
当一个线程占用资源A而请求资源B,另一个线程占用资源B而请求资源A,两个线程都无法继续运行,这种情况叫做死锁。
下面的方法用来解决线程间数据传递的问题(主要是竞争带来的问题,死锁主要依赖于编程技巧而不是编程技术来解决问题)。
1、原子操作
原子操作是在执行完成之前不会被打断的表达式,Interlocked类提供了一些静态函数作为原子操作。
2、Lock
锁机制可以将一段代码保护起来,只有一个线程能在期间运行,就像一个房子只有一个人可以在里面,其解决方案如下:
门是一些特别的函数(例如Monitor.Enter),这些特别的函数保证了当前语句只有锁是开着的才能继续执行后面的代码,否则线程将进入阻塞状态,直到它获得钥匙为止。
锁是引用类型的对象,实际上可以用一个整型的引用计数来表示锁的状态,如果整数为0,表示锁是打开的,其余整数表示锁是关闭的(如果状态只有两种,锁也可以用bool类型来表示),但C#的实现方式是引用类型对象。
例如:
lock(syncObject)
{
index++;
if (index == 10)
signal.Set();
Console.WriteLine("Received result.");
}
3、Monitor类
Monitor类包含很多获取和释放同步锁的方法。
要获得锁,需要调用Monitor.Enter,它将阻塞直到获得锁为止,要释放锁,需要调用Monitor.Exit。lock的本质也是通过调用Monitor.Enter和Monitor.Exit来实现的。
Monitor的TryEnter方法不会使得调用线程因为无法获得锁而阻塞,如果没有获得锁,它将返回false。
Monitor的Wait方法释放当前锁,并使得当前线程进入WaitSleepJoin状态,要使得线程退出WaitSleepJoin状态,其它线程需要对锁对象调用Pulse方法。
4、Volatile
编译器为了提高性能通常会进行优化,其中一种方式就是将经常使用的变量会被放入寄存器,然后在多线程程序中,另一个线程会对该变量在内存中的值进行改变,从而产生竞争。为变量加上volatile保证编译器对变量访问不进行优化,而且保证每次都从内存中读取变量的值。
volatile变量并不保证它是线程安全的,它只是在内存指令级别保证其是线程安全的,并没有从程序指令级约束它是线程安全的。