最近的项目中涉及到实时数据的处理,经常会使用多线程访问共享资源。如果处理不当,资源未能正确在各个线程中同步的话,计算结果将会出现错误。
关于资源同步最常用的技术就是加锁。这里提到是一个比较简单的锁 -- lock。 lock是对monitor中的两个函数enter和exit的封装。
当时项目的模式是这样的:有一个类中有个共享的资源(List),这个类会开辟两个线程分别对它进行读和写操作,而且这个类会有多个实例,每个实例都会在一个线程中。
其工作模式如下图所示:
这里的写线程将会动态改变list里面的值已经值对应的index,而读的线程会先用一个匹配的函数找出需要的值对应的index,然后根据index取出其中的值加以计算。
在这种模式中,如果读线程去访问list数据时,写线程对list进行了修改,就会导致结果出错。因此我们需要引入锁的机制。那么问题来了,我们是要在读的线程中加锁,还是在写的线程中加锁。而且加锁的话,是采用什么方式?
以下有几种方式,将会一一分析其对错。
1. 使用对象本身作为锁对象,对写线程进行加锁
使用这种方式,在写线程中,list将会被锁定,执行完毕之后释放锁。然而这种模式还是会得出错误结果,假设在读线程中先对list进行了匹配并取出了需要的index,这时写线程获取了锁,并对list进行了修改,之后释放list,读线程根据index查找list对应的值,然而此时list已被更改。
2. 使用对象本身作为锁对象,对读线程进行加锁
使用这种方式,在读线程中,list将会被锁定,所以读的过程中list都将保持不变。
除了使用对象本身作为锁对象之外,还可以使用其他的引用类型,比如System.Object,此时线程不会访问该类型的任何属性和方法,改对象的作用仅仅是协调各个线程。
3. 使用System.Object作为锁对象
根据上面的分析,我们将只对读线程进行加锁。但是这时又有两种情况:object对象可以定义成实例对象和静态对象。目前的这种模式适用于把锁加在实例对象的object上。因为使用object作为锁对象时,操作流程时占有A,操作B,释放A。若将object定义为静态对象,此时对object进行加锁,将协调的是类的每个实例所产生的对象,对应的list也必须是静态的。而这里我们需要协调的是某一个实例中的两个线程,因此需要将object定义为实例对象。
综上所述,有两种方式可以实现以上模式的线程同步,一种是使用对象本身作为锁对象,对读线程进行加锁,另一种是使用实例对象的System.Object作为锁对象,对读线程进程加锁。