1.最简单的多线程调用
System.Threading.Thread类构造方法接受一个ThreadStart委托,改委托不带参数,无返回值
1 public static void Start1() 2 { 3 Console.WriteLine("this is main thread!:{0},{1}", System.Threading.Thread.CurrentThread.CurrentCulture, Thread.CurrentThread.Name); 4 System.Threading.ThreadStart start = Method1; 5 Thread thread = new Thread(start); 6 thread.IsBackground = true; 7 thread.Start(); 8 Console.WriteLine("main thread other thing..."); 9 } 10 public static void Method1() 11 { 12 Console.WriteLine("this is sub thread!:{0},{1}", System.Threading.Thread.CurrentThread.CurrentCulture, Thread.CurrentThread.Name); 13 Thread.Sleep(TimeSpan.FromSeconds(3)); 14 Console.WriteLine("sub thread other thing..."); 15 }
注意thread.IsBackground=true,利用Thread创建的线程默认是前台线程,即IsBackground=false,而线程池中的线程是后台线程。
前台线程和后台线程的区别在于:当主线程执行结束时,若任然有前台线程在执行,则应用程序的进程任然处于激活状态,直到前台线程执行完毕;而换成后台线程,当主线程结束时,后台线程也跟着结束了。
2.给线程传送数据
这是使用ParameterizedThreadStart 委托来代替ThreadStart委托,ParameterizedThreadStart 委托接受一个带object的参数,无返回值
1 public static void Start2() 2 { 3 Customer c = new Customer { ID = "aaa", Name = "name" }; 4 Console.WriteLine("this is main thread!:{0},{1}", System.Threading.Thread.CurrentThread.CurrentCulture, Thread.CurrentThread.Name); 5 ParameterizedThreadStart start = Method2; 6 Thread thread = new Thread(start); 7 thread.Start(c); 8 Console.WriteLine("main thread other thing..."); 9 } 10 public static void Method2(object o) 11 { 12 Console.WriteLine("this is sub thread!:{0},{1}", System.Threading.Thread.CurrentThread.CurrentCulture, Thread.CurrentThread.Name); 13 Console.WriteLine(o.ToString()); 14 Thread.Sleep(TimeSpan.FromSeconds(3)); 15 Console.WriteLine("sub thread other thing..."); 16 }
由此实例可以看出,我们将一个Customer 实例传入了新线程中,新线程可以直接读取此参数的信息。
当然还有另一种方法也可以将数据传入线程中,创建一个类,把线程的方法定义为实例方法,这样就可以初始化实例的数据,之后启动线程,还是看实例代码:
1 public static void Start4() 2 { 3 Customer c = new Customer(); 4 //调用同一个对象,从而实现资源共享 5 ThreadStart ts = c.Increase; 6 Thread[] tArray = new Thread[20]; 7 for (int i = 0; i < 20; i++) 8 { 9 tArray[i] = new Thread(ts); 10 tArray[i].Start(); 11 } 12 for (int i = 0; i < 20; i++) 13 { 14 tArray[i].Join(); 15 } 16 Console.WriteLine(c.Number.ToString()); 17 } 18 public static void Method3(object o) 19 { 20 Customer c = o as Customer; 21 //若不上锁,所以每次结果都不同 22 //应该重新建立一个object进行上锁,因为外边还有可能访问到c这个实例 23 lock (c) 24 { 25 for (int j = 0; j < 1000; j++) 26 { 27 c.Number++; 28 } 29 } 30 }
Customer类的定义如下:
1 public class Customer 2 { 3 public int Number 4 { 5 get; 6 set; 7 } 8 public string ID 9 { 10 get; 11 set; 12 } 13 14 public string Name 15 { 16 get; 17 set; 18 } 19 public Customer() 20 { 21 Number = 0; 22 } 23 public void Increase() 24 { 25 object o = new object(); 26 lock (o) 27 { 28 for (int i = 0; i < 1000; i++) 29 { 30 Number++; 31 } 32 } 33 } 34 }
3.竞态条件
来看竞态条件的定义: 如果两个或多个线程访问相同的对象,或者访问不同步的共享状态,就会出现竞态条件。
竞态条件也是多线程编程的常犯的错误,如果代码不够健壮,多线程编码会出现一些预想不到的结果,我们来根据一个实例来看:
1 public static void RaceCondition() 2 { 3 ThreadStart method = ChangeState; 4 //这里放出20个线程 5 for (int i = 0; i < 20; i++) 6 { 7 Thread t = new Thread(method); 8 t.Name = i.ToString() + "aa"; 9 t.Start(); 10 } 11 } 12 //2.线程调用的方法,改变状态值 13 public static void ChangeState() 14 { 15 for (int loop = 0; loop < 1000; loop++) 16 { 17 int state = 5; 18 if (state == 5) 19 { 20 //此处第一个线程进入后没来得及++操作,第二个线程又进入,此时第一个线程做了++操作,第二个 21 //线程继续++,state的值变成7 22 state++; 23 if (state == 7) 24 { 25 //没有试验成功 26 Console.WriteLine("state={0},loop={1}", state, loop); 27 Console.WriteLine("thread name:{0}", Thread.CurrentThread.Name); 28 } 29 //Console.WriteLine(state.ToString()); 30 } 31 } 32 }
最简单的解决竞态条件的办法就是使用上锁-lock,锁定共享的对象。用lock语句锁定在线程中共享的变量state,只有一个线程能在锁定块中处理共享的state对象。由于这个对象由所有的线程共享,因此如果一个线程锁定了state,另一个线程就必须等待该锁定的解除。
1 public static void ChangeState2() 2 { 3 object o = new object(); 4 for (int loop = 0; loop < 100; loop++) 5 { 6 int state = 5; 7 lock (o) 8 { 9 if (state == 5) 10 { 11 state++; 12 if (state == 7) 13 { 14 //没有试验成功 15 Console.WriteLine("state={0},loop={1}", state, loop); 16 Console.WriteLine("thread name:{0}", Thread.CurrentThread.Name); 17 } 18 } 19 } 20 } 21 }
4.死锁
在死锁中,至少有两个线程被挂起,等待对方解除锁定。由于两个线程都在等待对方,就出现了死锁,线程将无限等待下去。
5.几种同步方法
上面介绍了两种线程数据共享的办法,一旦需要共享数据,就必须使用同步技术,确保一次只有一个线程访问和改变共享状态。上面介绍了使用lock的方法防止竞态条件的发生,但是如果用不好的话会产生死锁。那么下面再介绍几种针对不同情况使用的线程同步方法。
(1)SyncRoot模式
下面创建一个类的两个版本,一个同步版本,一个异步版本
1 public class GeneralDemo 2 { 3 public virtual bool IsSynchronized 4 { 5 get { return false; } 6 } 7 public static GeneralDemo Synchronized(GeneralDemo demo) 8 { 9 if (demo.IsSynchronized) 10 { 11 return new SyncDemo(demo); 12 } 13 return demo; 14 } 15 public virtual void DoThis() 16 { } 17 public virtual void DoThat() 18 { } 19 }
1 //同步版本 2 private class SyncDemo : GeneralDemo 3 { 4 private object syncRoot = new object(); 5 private GeneralDemo demo; 6 private int state = 0; 7 8 public int State 9 { 10 get { return state; } 11 set { state = value; } 12 } 13 public SyncDemo(GeneralDemo demo) 14 { 15 this.demo = demo; 16 } 17 public override bool IsSynchronized 18 { 19 get 20 { 21 return true; 22 } 23 } 24 public override void DoThat() 25 { 26 lock (syncRoot) 27 { 28 demo.DoThis(); 29 } 30 } 31 public override void DoThis() 32 { 33 lock (syncRoot) 34 { 35 demo.DoThis(); 36 } 37 }
需要注意的是在SyncDemo类中,只有方法是同步的,对于这个类的成员调用并没有同步,如果试图用SyncRoot模式锁定对属性的访问,对state的访问变成线程安全的,仍会出现竞态条件
即这样做是不可取的:
1 //public int State 2 //{ 3 // get { lock (syncRoot) { return state; } } 4 // set { lock (syncRoot) { state = value; } } 5 //}
1 public int State 2 { 3 get 4 { 5 return Interlocked.Increment(ref state); 6 } 7 }
(3)Monitor类
1 public override void DoThis() 2 { 3 if (Monitor.TryEnter(syncRoot, 500)) 4 { 5 try 6 { 7 //acquired the lock 8 //synchroized region for syncRoot 9 } 10 finally 11 { 12 Monitor.Exit(syncRoot); 13 } 14 } 15 else 16 { 17 //didn't get the lock,do something else 18 } 19 }