1、如果两个或多个线程访问相同的对象,或者访问不同步的共享状态,会出现争用条件。
1 using System; 2 using System.Text; 3 using System.Threading; 4 5 class Outputer 6 { 7 public void Output(string msg) 8 { 9 for (int i = 0; i < msg.Length; i++)10 {11 Console.Write(msg[i]);12 }13 Console.WriteLine();14 }15 }16 17 class Program18 {19 static void Main(string[] args)20 {21 Outputer outputer = new Outputer();22 object locker = new object();23 StringBuilder str = new StringBuilder();24 for (int i = 0; i < 26; i++)25 {26 str.Append(((char)('A' + i)).ToString());27 }28 new Thread((msg) =>29 {30 while (true)31 {32 outputer.Output(msg.ToString());33 }34 }).Start(str.ToString());35 new Thread(() =>36 {37 while (true)38 {39 outputer.Output("1234567890");40 }41 }).Start();42 }43 }
运行结果:
2、要避免该问题,可以使用lock语句锁定共享的对象。
1 using System; 2 using System.Text; 3 using System.Threading; 4 5 class Outputer 6 { 7 public void Output(string msg) 8 { 9 for (int i = 0; i < msg.Length; i++)10 {11 Console.Write(msg[i]);12 }13 Console.WriteLine();14 }15 }16 17 class Program18 {19 static void Main(string[] args)20 {21 Outputer outputer = new Outputer();22 object locker = new object();23 StringBuilder str = new StringBuilder();24 for (int i = 0; i < 26; i++)25 {26 str.Append(((char)('A' + i)).ToString());27 }28 new Thread((msg) =>29 {30 while (true)31 {32 lock (locker)33 {34 outputer.Output(msg.ToString());35 }36 }37 }).Start(str.ToString());38 new Thread(() =>39 {40 while (true)41 {42 lock (locker)43 {44 outputer.Output("1234567890");45 }46 }47 }).Start();48 }49 }
运行结果:
3、也可以将共享对象设置为线程安全的对象。
1 using System; 2 using System.Text; 3 using System.Threading; 4 5 class Outputer 6 { 7 public void Output(string msg) 8 { 9 lock (this)10 {11 for (int i = 0; i < msg.Length; i++)12 {13 Console.Write(msg[i]);14 }15 Console.WriteLine();16 }17 }18 }19 20 class Program21 {22 static void Main(string[] args)23 {24 Outputer outputer = new Outputer();25 object locker = new object();26 StringBuilder str = new StringBuilder();27 for (int i = 0; i < 26; i++)28 {29 str.Append(((char)('A' + i)).ToString());30 }31 new Thread((msg) =>32 {33 while (true)34 {35 outputer.Output(msg.ToString());36 }37 }).Start(str.ToString());38 new Thread(() =>39 {40 while (true)41 {42 outputer.Output("1234567890");43 }44 }).Start();45 }46 }
4、过多的锁定会造成死锁。所谓死锁即是至少有两个线程被挂起,互相等待对方解锁,以至于线程无限等待下去。
1 using System; 2 using System.Threading; 3 4 class DeadLocker 5 { 6 object locker1 = new object(); 7 object locker2 = new object(); 8 9 public void Method1()10 {11 while (true)12 {13 lock (locker1)14 {15 lock (locker2)16 {17 Console.WriteLine("First lock1, and then lock2");18 }19 }20 }21 }22 23 public void Method2()24 {25 while (true)26 {27 lock (locker2)28 {29 lock (locker1)30 {31 Console.WriteLine("First lock2, and then lock1");32 }33 }34 }35 }36 }37 38 class Program39 {40 static void Main(string[] args)41 {42 DeadLocker dl = new DeadLocker();43 new Thread(dl.Method1).Start();44 new Thread(dl.Method2).Start();45 }46 }
运行结果:
5、同步问题和争用条件以及死锁相关,要避免同步问题,最好就不要在线程之间共享数据。如果要共享数据就必须使用同步技术,确保一次只有一个线程访问和改变共享状态。在C#中,lock语句是设置锁定和解除锁定的一种简单方式。编译器将其编译为IL后,会被编译成了调用Monitor类的Enter和Exit方法。
1 using System; 2 using System.Threading; 3 4 class Program 5 { 6 static void Main(string[] args) 7 { 8 } 9 10 void Method()11 {12 lock (typeof(Program))13 {14 }15 }16 }
编译结果:
6、争用条件的另一个例子。
1 using System; 2 using System.Threading; 3 using System.Threading.Tasks; 4 5 class SharedState 6 { 7 public int State { get; set; } 8 } 9 10 class Worker11 {12 SharedState state;13 14 public Worker(SharedState state)15 {16 this.state = state;17 }18 19 public void DoJob()20 {21 for (int i = 0; i < 500; i++)22 {23 state.State += 1;24 }25 }26 }27 28 class Program29 {30 static void Main(string[] args)31 {32 int numTasks = 20;33 var state = new SharedState();34 var tasks = new Task[numTasks];35 for (int i = 0; i < numTasks; i++)36 {37 tasks[i] = new Task(new Worker(state).DoJob);38 tasks[i].Start();39 }40 for (int i = 0; i < numTasks; i++)41 {42 tasks[i].Wait(); //使20个任务全部处于等待状态,直到所有任务都执行完毕为止43 }44 Console.WriteLine("Summarized {0}", state.State);45 }46 }
运行结果:
从上面结果看出,20个任务分别对共享的数据累加后,打印其结果。每个任务执行500次,共20个任务,理想的结果是10000,但是事实并非如此。事实是每次运行的结果都不同,且没有一个结果是正确的。使用lock语句时要注意的是传递的锁对象必须是引用对象,若对值对象使用lock语句,C#编译器会报错。
7、将上述代码改写为如下的SyncRoot模式,但是不能打印输出理想结果的10000。
1 using System; 2 using System.Threading; 3 using System.Threading.Tasks; 4 5 class SharedState 6 { 7 public int State { get; set; } 8 } 9 10 class Worker11 {12 SharedState state;13 14 public Worker()15 {16 this.state = new SharedState(); 17 }18 19 public Worker(SharedState state)20 {21 this.state = state;22 }23 24 public static Worker Synchronized(Worker worker)25 {26 if (!worker.IsSynchronized)27 {28 return new SynchronizedWorker(worker);29 }30 return worker;31 }32 33 public virtual void DoJob()34 {35 for (int i = 0; i < 500; i++)36 {37 state.State += 1;38 }39 }40 41 public virtual bool IsSynchronized42 {43 get { return false; }44 }45 46 private class SynchronizedWorker : Worker47 {48 object locker = new object();49 Worker worker;50 51 public SynchronizedWorker(Worker worker)52 {53 this.worker = worker;54 }55 56 public override bool IsSynchronized57 {58 get { return true; }59 }60 61 public override void DoJob()62 {63 lock (locker)64 {65 worker.DoJob();66 }67 }68 }69 }70 71 class Program72 {73 static void Main(string[] args)74 {75 int numTasks = 20;76 var state = new SharedState();77 var tasks = new Task[numTasks];78 for (int i = 0; i < numTasks; i++)79 {80 Worker worker = Worker.Synchronized(new Worker(state));81 tasks[i] = new Task(worker.DoJob);82 tasks[i].Start();83 }84 for (int i = 0; i < numTasks; i++)85 {86 tasks[i].Wait(); //使20个任务全部处于等待状态,直到所有任务都执行完毕为止87 }88 Console.WriteLine("Summarized {0}", state.State);89 }90 }
将SharedState类也改写为SyncRoot模式,还是不行,不明白原因。
1 class SharedState 2 { 3 object locker = new object(); 4 5 int state; 6 7 public int State 8 { 9 get10 {11 lock (locker)12 {13 return this.state;14 }15 }16 set17 {18 lock (locker)19 {20 this.state = value;21 }22 }23 }24 }
最简单且可靠的办法是在DoJob方法中,将lock语句添加到合适的地方。
1 using System; 2 using System.Threading; 3 using System.Threading.Tasks; 4 5 class SharedState 6 { 7 public int State { get; set; } 8 } 9 10 class Worker11 {12 SharedState state;13 14 public Worker(SharedState state)15 {16 this.state = state;17 }18 19 public void DoJob()20 {21 for (int i = 0; i < 500; i++)22 {23 // 最简单可靠的办法是在适合的地方添加lock语句24 lock (state)25 {26 state.State += 1;27 }28 }29 }30 }31 32 class Program33 {34 static void Main(string[] args)35 {36 int numTasks = 20;37 var state = new SharedState();38 var tasks = new Task[numTasks];39 for (int i = 0; i < numTasks; i++)40 {41 tasks[i] = new Task(new Worker(state).DoJob);42 tasks[i].Start();43 }44 for (int i = 0; i < numTasks; i++)45 {46 tasks[i].Wait(); //使20个任务全部处于等待状态,直到所有任务都执行完毕为止47 }48 Console.WriteLine("Summarized {0}", state.State);49 }50 }
或者也可以如下重写DoJob方法。
1 public void DoJob() 2 { 3 // 最简单可靠的办法是在适合的地方添加lock语句 4 lock (state) 5 { 6 for (int i = 0; i < 500; i++) 7 { 8 state.State += 1; 9 }10 }11 }
注意:在一个地方使用lock语句并不意味着,访问对象的其他线程都正在等待。必须对每个访问共享状态的线程显示地使用同步功能。
8、Interlocked类是一个静态类型,用于使简单的语句原子化,例如,i++不是线程安全的,它的操作包括:从内存中获取一个值,给该值递增1,再将它存储回内存。所有这些操作都有可能被线程调试器打断。
1 using System; 2 using System.Threading; 3 using System.Threading.Tasks; 4 5 class SharedState 6 { 7 private int state; 8 public int State 9 {10 get { return this.state; }11 set { this.state = value; }12 }13 14 public void Increment()15 {16 Interlocked.Increment(ref state); //替代this.state++;并且是线程安全的17 }18 }19 20 class Worker21 {22 SharedState state;23 24 public Worker(SharedState state)25 {26 this.state = state;27 }28 29 public void DoJob()30 {31 for (int i = 0; i < 500; i++)32 {33 state.Increment();34 }35 }36 }37 38 class Program39 {40 static void Main(string[] args)41 {42 int numTasks = 20;43 var state = new SharedState();44 var tasks = new Task[numTasks];45 for (int i = 0; i < numTasks; i++)46 {47 tasks[i] = new Task(new Worker(state).DoJob);48 tasks[i].Start();49 }50 for (int i = 0; i < numTasks; i++)51 {52 tasks[i].Wait(); //使20个任务全部处于等待状态,直到所有任务都执行完毕为止53 }54 Console.WriteLine("Summarized {0}", state.State);55 }56 }