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 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 }

浙公网安备 33010602011771号