C#中的线程四(System.Threading.Thread)
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 Name15 {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的值变成722 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 IsSynchronized18 {19 get20 {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 State2 //{3 // get { lock (syncRoot) { return state; } }4 // set { lock (syncRoot) { state = value; } }5 //}
1 public int State2 {3 get4 {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 finally11 {12 Monitor.Exit(syncRoot);13 }14 }15 else16 { 17 //didn't get the lock,do something else18 }19 }