深入分析C# 线程同步

2020-06-17 15:01:08于丽

上一篇介绍了如何开启线程,线程间相互传递参数,及线程中本地变量和全局共享变量区别。

本篇主要说明线程同步。

如果有多个线程同时访问共享数据的时候,就必须要用线程同步,防止共享数据被破坏。如果多个线程不会同时访问共享数据,可以不用线程同步。

线程同步也会有一些问题存在:

性能损耗。获取,释放锁,线程上下文建切换都是耗性能的。 同步会使线程排队等待执行。

线程同步的几种方法:

阻塞

当线程调用Sleep,Join,EndInvoke,线程就处于阻塞状态(Sleep使调用线程阻塞,Join、EndInvoke使另外一个线程阻塞),会立即从cpu退出。(阻塞状态的线程不消耗cpu)

当线程在阻塞和非阻塞状态间切换时会消耗几毫秒时间。

//Join
static void Main()
{
 Thread t = new Thread (Go);
 Console.WriteLine ("Main方法已经运行...."); 
 t.Start();
 t.Join();//阻塞Main方法
 Console.WriteLine ("Main方法解除阻塞,继续运行...");
}
 
static void Go()
{
 Console.WriteLine ("在t线程上运行Go方法..."); 
}

//Sleep
static void Main()
{
 Console.WriteLine ("Main方法已经运行...."); 
 Thread.CurrentThread.Sleep(3000);//阻塞当前线程
 Console.WriteLine ("Main方法解除阻塞,继续运行...");
}
 
 //Task
 static void Main()
{
 Task Task1=Task.Run(() => { 
  Console.WriteLine("task方法执行..."); 
  Thread.Sleep(1000);
  }); 
 Console.WriteLine(Task1.IsCompleted);  
 Task1.Wait();//阻塞主线程 ,等该Task1完成
 Console.WriteLine(Task1.IsCompleted); 
}

加锁(lock)

加锁使多个线程同一时间只有一个线程可以调用该方法,其他线程被阻塞。

同步对象的选择:

使用引用类型,值类型加锁时会装箱,产生一个新的对象。 使用private修饰,使用public时易产生死锁。(使用lock(this),lock(typeof(实例))时,该类也应该是private)。 string不能作为锁对象。 不能在lock中使用await关键字

锁是否必须是静态类型?

如果被锁定的方法是静态的,那么这个锁必须是静态类型。这样就是在全局锁定了该方法,不管该类有多少个实例,都要排队执行。

如果被锁定的方法不是静态的,那么不能使用静态类型的锁,因为被锁定的方法是属于实例的,只要该实例调用锁定方法不产生损坏就可以,不同实例间是不需要锁的。这个锁只锁该实例的方法,而不是锁所有实例的方法.*

class ThreadSafe
{
 private static object _locker = new object();
 
 void Go()
 {
 lock (_locker)
 {
 ......//共享数据的操作 (Static Method),使用静态锁确保所有实例排队执行
 }
 }

private object _locker2=new object();
 void GoTo()
 {
 lock(_locker2)
 //共享数据的操作,非静态方法,是用非静态锁,确保同一个实例的方法调用者排队执行
 }
}