源文来源 :http://www.cnblogs.com/jackson0714/p/5100372.html
一、多线程介绍
C#通过多线程支持并行执行的代码。一个线程是一个独立执行的路径,可以同时与其他线程一起运行。一个C#客户端程序(Console,WPF,Winows
Forms)开始于一个单独的线程,该线程由CLR和操作系统自动地创建,我们称它为主线程,而且可以通过创建附加的线程来实现多线程。
1.一个简单的栗子
class Program
2 {
3 static void Main(string[] args)
4 {
5 Thread thread = new Thread(WriteY);//创建一个线程
6 thread.Start();//开始一个线程
7
8 for (int i = 0; i < 1000; i++)//主线程执行循环
9 {
10 Console.Write("x");
11 }
12
13 Console.ReadLine();
14 }
15 static void WriteY()
16 {
17 for (int i = 0; i < 1000; i++)
18 {
19 Console.Write("y");
20 }
21 }
22
23 }
一旦开始,一个线程的IsAlive属性返回true,直到这个线程结束。当传递给线程的构造函数的委托完成执行时,这个线程结束。一旦结束,这个线程不能重启。IsAlive:判断线程是否还活着
2.内存隔离
CLR给每个线程分配自己内存栈,因此局部变量可以保持分离。在下面例子中,我们定义了一个使用局部变量的方法,然后在主线程和子线程同时调用这个方法
class Program
{
static void Main(string[] args)
{
new Thread(Go).Start();
Go();
Console.ReadKey();
}
static void Go()
{
for (int i = 0; i < 5; i++)
{
Console.Write("y");
}
}
}
运行结果:
3.数据共享
如果多个线程对同一个对象实例有相同的引用,这些线程就共享这个对象实例的数据。
class ThreadTest
{
static bool done; // 静态字段在所有线程之间共享
static void Main()
{
new Thread (Go).Start();
Go();
}
static void Go()
{
if (!done)
{
done = true;
Console.WriteLine ("Done");
}
}
}
4.线程安全
上面两个例子展示了另外一个重要的概念:线程安全确实是不确定的:done可能被打印出两次(尽管是不太可能发生的)。当我们把Go方法中的语句的顺序交换下,打印出两次done的几率显著提升
class ThreadTest
{
static bool done; // 静态字段在所有线程之间共享
static void Main()
{
new Thread (Go).Start();
Go();
}
static void Go()
{
if (!done)
{
Console.WriteLine ("Done");
done = true;
}
}
}
这个地方的问题是线程A在线程B设置done等于true之前进入if条件判断中,所有A有机会打印出"Done"。
改进方式: 当读\写一个公共字段时,获取一个独占锁(exclusive lock)。C#提供了关键字lock。
class Program
{
static bool done = false;
static readonly object locker = new object();
static void Main(string[] args)
{
new Thread(Go).Start();
Go();
Console.ReadKey();
}
static void Go()
{
lock (locker)
{
if (!done)
{
Console.WriteLine("Done");
done = true;
}
}
}
}
当两个线程同时抢占一个锁时(在这个例子中,locker),一个线程等待,或者阻塞,知道这个锁释放。在这个例子中,这个锁保证一次只有一个线程可以进入代码的临界区域,然后“Done”只会被打印一次。代码在这种不确定的多线程背景下中被保护被叫做线程安全。
注意:在多线程中,共享数据是造成复杂原因的主要,而且会产生让人费解的错误。尽管很基本但还是要尽可能保持简单。
一个线程,当阻塞的时候,不占用CPU资源。
二、Join 和Sleep
1.Join
当程序运行时,启动了一个耗时较长的线程来打印数字,打印每个数字前要等待两秒。但我们在主程序中调用了t.Join方法,该方法允许我们等待直到线程t完成。当线程t完成 "时,主程序会继续运行。借助该技术可以实现在两个线程间同步执行步骤。第一个线程会等待另一个线程完成后再继续执行。第一个线程等待时是处于阻塞状态(正如暂停线程中调用 Thread.Sleep方法一样),。
static void Main(string[] args)
{
Thread t = new Thread(Print);
t.Start();
t.Join();
MainPrint();
}
static void MainPrint()
{
Console.WriteLine("主线程执行");
}
static void Print()
{
Console.WriteLine("线程执行");
}
当使用了Join方法后,等待Print执行完毕后,才执行MainPrint
2.Sleep
Thread.Sleep暂停当前线程一段指定的时间:
Thread.Sleep(TimeSpan.FromHours(1));//sleep一个小时
Thread.Sleep(500);//sleep 500 微秒
当使用Sleep或Join暂停线程时,这个线程是阻塞的,不消耗CPU资源
static void Main(string[] args)
{
Thread t = new Thread(Print);
t.Start();
Thread.Sleep(TimeSpan.FromSeconds(5));
MainPrint();
}
static void MainPrint()
{
Console.WriteLine("主线程执行");
}
static void Print()
{
Console.WriteLine("线程执行");
}
结果是Print先执行,5秒后,MainPrint才执行
Thread.Sleep(0)立即放弃这个线程的时间片,主动交出CPU给其他线程。Framework 4.0的新方法Thread.Yield()方法做同样的事,除了当它仅仅在同一个进程中时,才会放弃时间片。
Sleep(0)或Yield()有时候对提升产品性能有用。而且它们也是诊断工具可以帮助揭开线程安全的问题; 如果在代码中的任何地方都插入Thread.Yield(),会造成bug。