C#中的多线程--持续更新系列
今年第一次旅行结束,虽然是第二次进去藏区,依旧有高原反应,嚓....不过整个旅途感受到前所未有的放松.更有精力面对接下来的工作和学习.进入今天的主题---C#中的多线程
1、感受多线程
因为文章追求是简单易懂,如果您和我一样,是C#初学者,那么在这一段中,请跟着例子,写一次,每个例子都有分析,并且这里的例子是会把多线程涉及的很多问题先引入出来,在后面的阶段,再深入分析~
C#是支持多线程滴(貌似是废话.)~一个线程有它独立的执行路径,能够与其他的线程“同时”运行,一个C#程式起始于一个单线程,这个线程是被CLR和操作系统自动创建滴。~啰嗦了这么多了,还是用一段简单的代码加以说明
class Program { static void Main(string[] args) { Thread t = new Thread(WriteYes); t.Start(); while (true) { Console.Write("No"); } } static void WriteYes() { while (true) { Console.Write("Yes"); } } }
程序运行截图
程序分析:在主线程中创建了一个新的线程"t",完成的任务是重复的输出"yes",同时主线程重复的打印"No",在这里并没有控制线程的执行,所以看到的运行结果是随机的。
首先,先看一段书本上的:CLR分配每个线程到自己的内存堆栈上,来保证局部变量的分离运行
static void Main(string[] args) { new Thread(Go).Start(); Go(); } static void Go() { for (int cycles = 0; cycles < 5; cycles++) { Console.Write("??"); } }
程序分析:在此程式中,声明了一个方法Go,在Go这个方法中声明并使用了一个局部变量'cycles',在主程序中,又开启了一个线程(匿名线程)调用方法Go,和在主线程直接调用Go~通过运行结果可以看出,一共输出了10个"?"
由此,可见变量'cycles'分别在自己的内存堆栈中创建,互不影响!那是不是所有的情况都是如此的呢?答案显然是否定的
线程中引用共用的实例,共享数据问题,还是先看代码,通过代码来分析
#region 多线程共享实例 bool done; static void Main(string[] args) { Program pp = new Program();//创建了一个Program的实例 new Thread(pp.Go).Start(); pp.Go(); } //这里和上面的“线程间局部变量”中的Go方法不一定,这里是一个实例的方法, //也就是需要new Program一个实例才可以调用 void Go() { if (!done) { done = true; Console.WriteLine("done"); } } #endregion
程序分析:在主程序中,声明了一个Program实例pp,在相同的Program实例中,两个线程都调用了方法Go,此时共享了done字段,所以程序运行只输出一个"done",而不是两个
静态字段在多线程中的影响
#region 多线程中的静态字段 static bool done; static void Main(string[] args) { new Thread(Go).Start(); Go(); } static void Go() { if (!done) { done = true; Console.WriteLine("done"); } } #endregion
程序运行结果:
看到这里,也许你会想,静态字段和实例字段在多线程中是否没有任何影响,稍等,我们将上面的代码进行简单的修改,将Go方法修改如下
static void Go() { if (!done) { Console.WriteLine("done"); done = true; } }
在多次执行这个程式,输出done的次数在1和2次之间没规律的切换~
程序分析:从运行的结果上看,这个输出的次数就不受我们程序的控制,问题就是在一个线程在执行if判断的时候,恰好此时另一个线程正在执行输出语句WriteLine语句.当这个线程执行完WriteLine语句,在将done设置为true之前,执行if判断的线程发现done还是为false,所以也会执行WriteLine语句.导致了问题的产生----线程安全问题
线程安全问题
那么如何解决上面遇到的问题呢?抛开程序来说,我们两个人去做一件事,那么为了避免重复,我们可以这样,先获得做事权限的人,先设置一个标志,告诉后面来的人,我现在正在执行任务,请不要进入现场。程序中也是这样的原理,在C#中提供了lock语句来实现(锁的机制)
#region 线程安全 static bool done; static object locker = new object(); static void Main(string[] args) { new Thread(Go).Start(); Go(); Console.Read(); } static void Go() { lock (locker) { if (!done) { Console.WriteLine("done"); done = true; } } } #endregion
我们再试着多次运行程序,会发现只会输出一个done,当两个线程在争夺一个锁的时候(例子中的是locker),一个线程会处于等待或者说是阻止状态知道那个锁变成可用,确保在同一时刻只有一个线程进入了工作区,所以保证只输出了一次done.
在完成上面几个简单的例子之后,我们应该思考,在何时使用多线程~
何时使用多线程
其实这个话题,我想是仁者见仁智者见智了,但核心的思想都是一致的,多线程程序一般被用来在后台处于耗时的任务,主线程保持运行,并且工作线程在后台默默无闻的工作,对于Winform程序来说,如果主线程(UI线程)执行一段耗时操作的代码,键盘和鼠标的操作会变得很迟钝,时间稍微长点,就整个程序就失去了响应。