假设多个线程共享一个静态变量,如果让每个线程都执行相同的方法每次让静态变量自增1,这样的做法线程安全吗?能保证自增变量数据同步吗?本篇体验使用lock语句块和Interlocked类型方法保证自增变量的数据同步。
□ 线程不安全、数据不同步的做法
class Program{static int sum = 0;static void Main(string[] args){Stopwatch watch = new Stopwatch();watch.Start();Parallel.For(0, Environment.ProcessorCount, i =>{for (int j = 0; j < 100000000; ++j){AddOne();}});watch.Stop();Console.WriteLine("sum={0},用了{1}", sum, watch.Elapsed);Console.ReadKey();}static void AddOne(){sum++;}}
○ 变量sum是静态的,供所有线程共享
○ Parallel.For提供并行循环, Environment.ProcessorCount表示处理器的处理,如果有4个CPU,就做4组循环
我们发现,结果不是我们期望的400000000,也就是说,在这种情况下的静态变量自增不是线程安全的,换句话说,无法保证共享数据的同步。
□ 通过lock语句块保持数据同步
lock语句块能保证在同一时间只有一个线程进入其中。class Program{static int sum = 0;private static readonly object o = new object();static void Main(string[] args){Stopwatch watch = new Stopwatch();watch.Start();Parallel.For(0, Environment.ProcessorCount, i =>{for (int j = 0; j < 100000000; ++j){AddOne();}});watch.Stop();Console.WriteLine("sum={0},用了{1}", sum, watch.Elapsed);Console.ReadKey();}static void AddOne(){lock (o){sum++;}}}
这一次,使用lock语句块得到了预期的结果。
□ 使用Interlocked保持数据同步
对于int或long类型变量的自增,并且保证类型安全,可以使用Interlocked类。提供的方法包括:
○ int Interlocked.Increment(ref int location),自增1
○ long Interlocked.Increment(ref long location),自增1
○ int Interlocked.Decrement(ref int location),自减1
○ long Interlocked.Decrement(ref long location),自减1
○ int Interlocked.Add(ref int location, int value),自增一个值
○ long Interlocked.Add(ref long location, long value),自增一个值
class Program{static int sum = 0;static void Main(string[] args){Stopwatch watch = new Stopwatch();watch.Start();Parallel.For(0, Environment.ProcessorCount, i =>{for (int j = 0; j < 100000000; ++j){AddOne();}});watch.Stop();Console.WriteLine("sum={0},用了{1}", sum, watch.Elapsed);Console.ReadKey();}static void AddOne(){Interlocked.Increment(ref sum);}}
使用Interlocked也能保证线程安全、数据同步,但耗时较长。
总结:
○ lock语句块和Interlocked类型方法都能保证自增变量的线程安全、数据同步
○ Interlocked类型方法只适用于int,long类型变量,有一定的局限性
线程系列包括: