这里的总结一下一些线程同步常用的方法,本篇基本都是基于对象的加锁来进行线程同步的。
volatile关键字
volatile关键字算是线程同步中最简单的一个了。基本思想是,由于编译器会对数据的存取有一定的优化,例如:
int a = i;
由于编译器判断在对a和i赋值期间没有其它操作,就会暂存i的值,在给a赋值时不会去真正读取i的值,而是使用暂存的i值。但是如果在多线程或者硬件编程的时候,i值是有可能在这两句话之间发生变化的,因此我们需要去读取i真实的值。
int a = i;
这样就可以避免这个问题,在每次使用i值时,都会去读取i的真实值,阻止了编译器的优化。volatile只能对应用于这些类型:
- Reference types.
- Pointer types (in an unsafe context). Note that while the pointer itself can be volatile, the object that it points to cannot. In other words, you cannot declare a "pointer to volatile."
- Integral types such as sbyte, byte, short, ushort, int, uint, char, float, and bool.
- An enum type with an integral base type.
- Generic type parameters known to be reference types.
- and .
volatile关键字只能被用于类或者结构的数据成员,不能用于局部变量。
Interlocked类
Interlocked 类只能用于整数变量的同步,同时多用于加减操作。
Interlocked.Increment( ref counter); // counter--
Interlocked.Add( ref counter, 2 ); // counter+=2
Interlocked.Exchange( ref counter, 2 ); // counter=2
这些方法都是作为原子操作完成,如果在执行过程中有其他线程请求counter,都将处于等待状态,直到操作完成。
Moniter类
Moniter.Enter(object)
获得一个对象的锁,如果该对象已经被其他线程独占,则该线程阻塞直到其他线程释放对该对象的锁。
Moniter.Exit(object)
释放一个对象的锁。如果在一个线程中,多次对一个对象使用Enter(),那么也应该使用相同次数的Exit()来释放对其锁。
bool Moniter.TryEnter(object, int)
在一段时间内尝试获得某对象的锁,如果在该时间段内成功获得锁,返回true,否则返回false。如果使用无int或者TimeSpan参数的重载,则仅尝试一次。
Moniter.Wait() / Moniter.Pulse()
只有获得锁的线程能够使用Wait()。使用该方法会立刻释放该对象的锁(无论用过多少次Enter()都会执行同样次数的Exit()),然后重新等待去获得对象锁,一些重载方法指定了等待时间。如果成功获得对象锁,则返回true,否则返回false(没有指定等待时间的重载方法只能返回true,因为其只有在获得锁之后才能进行下去)。在获得对象锁之后,会立即对该对象执行之前同样次数的Enter(),进入到Wait()之前的状态。
执行Wait()之后的线程进入到wait queue内,直到获得对象锁的线程发送Pulse()指令,将wait queue的最首等待线程加入ready queue,PulseAll()指令将把wait queue里所有的等待线程加入ready queue。然后释放(Exit())对象锁之后,ready queue之内的第一个线程会获得对象锁并继续运行。如果没有执行Pulse,即没有将wait queue的线程加入ready queue,那么这些线程将一直处于等待状态不会得到运行。
lock关键字
有了Moniter类的基础,lock关键字就很简单了。
{
DoSomething();
}
这段代码等价于
System.Threading.Monitor.Enter(obj);
try
{
DoSomething();
}
finally
{
System.Threading.Monitor.Exit(obj);
}