线程、任务和同步学习笔记(五)
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 Program 18 { 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 Program 18 { 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 Program 21 { 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 Program 39 { 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 Worker 11 { 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 Program 29 { 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 Worker 11 { 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 IsSynchronized 42 { 43 get { return false; } 44 } 45 46 private class SynchronizedWorker : Worker 47 { 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 IsSynchronized 57 { 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 Program 72 { 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 get 10 { 11 lock (locker) 12 { 13 return this.state; 14 } 15 } 16 set 17 { 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 Worker 11 { 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 Program 33 { 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 Worker 21 { 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 Program 39 { 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 }