线程的概念:
线程(Thread)是进程中的基本执行单元,是操作系统分配CPU时间的基本单位,一个进程可以包含若干个线程,在进程入口执行的第一个线程被视为这个进程的主线程。在.NET应用程序中,都是以Main()方法作为入口的,当调用此方法时系统就会自动创建一个主线程。线程主要是由CPU寄存器、调用栈和线程本地存储器(Thread Local Storage,TLS)组成的。CPU寄存器主要记录当前所执行线程的状态,调用栈主要用于维护线程所调用到的内存与数据,TLS主要用于存放线程的状态信息。(即线程是CPU调度的基本单位)
多线程的优缺点:
当前CPU运行速度太快,硬件处理速度跟不上,所以操作系统进行分时间片管理。这样,从宏观角度来说是多线程并发的,因为CPU速度太快,察觉不到,看起来是同一时刻执行了不同的操作。但是从微观角度来讲,同一时刻只能有一个线程在处理。(单核CPU情况),所以对于使用多线程来处理,能够是程序执行更加高效,并且能够实现并发量。但是在使用多线程时候,并不是越多越好,频繁的去创建多线程容易造成内存的损耗,当要并发的代码段十分简单,创建多个线程需要进行上下文切换等,并不会比单个线程块
所以说线程是一把双刃剑。如:
(1)线程也是程序,所以线程需要占用内存,线程越多,占用内存也越多。
(2)多线程需要协调和管理,所以需要占用CPU时间以便跟踪线程。
(3)线程之间对共享资源的访问会相互影响,必须解决争用共享资源的问题。
(4)线程太多会导致控制太复杂,最终可能造成很多程序缺陷。
C#中的多线程编程
一、在默认的情况下,C#程序具有一个线程,此线程执行程序中以Main方法开始和结束的代码,Main()方法直接或间接执行的每一个命令都有默认线程(主线程)执行,当Main()方法返回时此线程也将终止。
二、在C#中,线程是使用Thread类处理的,该类在System.Threading命名空间中。使用Thread类创建线程时,只需要提供线程入口,线程入口告诉程序让这个线程做什么。通过实例化一个Thread类的对象就可以创建一个线程。创建新的Thread对象时,将创建新的托管线程。Thread类接收一个ThreadStart委托或ParameterizedThreadStart委托的构造函数。且有参的委托其参数类型必须为object类型
//C#创建多线程,可以使用静态成员方法,也可以使用成员方法。
MyThread mythread = new MyThread();
Thread th1 = new Thread(new ThreadStart(mythread.TestThreadFunc2));
Thread th2 = new Thread(new ThreadStart(MyThread.TestThreadFunc1));
Thread th3 = new Thread(new ParameterizedThreadStart(mythread.TestThreadFunc3));
th1.Start();
th2.Start();
th3.Start("parame");
Console.ReadLine();
}
class MyThread
{
public static void TestThreadFunc1()
{
for(int i = 0; i < 5; i++)
{
Console.WriteLine("I am a static func thread......");
}
}
public void TestThreadFunc2()
{
for(int i = 0; i < 5; i++)
{
Console.WriteLine("I am a sample func thread....");
}
}
public void TestThreadFunc3(object name )
{
for(int i = 0; i < 5; i++)
{
Console.WriteLine("I am a param func thread....{0}", name);
}
}
}
线程中常用的属性
属性 | 说明 |
---|---|
CurrentContext | 获取线程正在其中执行的当前上下文。 |
CurrentThread | 获取当前正在运行的线程。 |
ExecutionContext | 获取一个 ExecutionContext 对象,该对象包含有关当前线程的各种上下文的信息。 |
IsAlive | 获取一个值,该值指示当前线程的执行状态。 |
IsBackground | 获取或设置一个值,该值指示某个线程是否为后台线程。 |
IsThreadPoolThread | 获取一个值,该值指示线程是否属于托管线程池。 |
ManagedThreadId | 获取当前托管线程的唯一标识符。 |
Name | 获取或设置线程的名称。 |
Priority | 获取或设置一个值,该值指示线程的调度优先级。 |
ThreadState | 获取一个值,该值包含当前线程的状态。 |
相关属性说明
- ManagedThreadId是确认线程的唯一标识符,程序在大部分情况下都是通过Thread.ManagedThreadId来辨别线程的。而Name是一个可变值,在默认时候,Name为一个空值 Null,开发人员可以通过程序设置线程的名称,但这只是一个辅助功能。
- .NET为线程设置了Priority属性来定义线程执行的优先级别,里面包含5个选项,其中Normal是默认值。除非系统有特殊要求,否则不应该随便设置线程的优先级别。分别为Lowest、BelowNormal、Normal、AboveNormal、Highest。
- ThreadState可以检测线程是处于Unstarted、Sleeping、Running 等等状态,它比 IsAlive 属性能提供更多的特定信息。一个应用程序域中可能包括多个上下文,而通过CurrentContext可以获取线程当前的上下文。
当一个线程执函数执行完毕之后,其状态为Stopped,当其时间片未被分配为WaitSleepJoin状态。 - CurrentThread是最常用的一个属性,它是用于获取当前运行的线程的属性,包括线程ID,并且封装内存槽保存本地数据等。
- IsBackground,设置前台后台线程。通过代码可以直观的看出。
mythread1.Count = 10;
mythread2.Count = 20;
Thread frontThread = new Thread(new ThreadStart(mythread1.RunLoop));
frontThread.Name = "前台线程";
Thread backThread = new Thread(new ThreadStart(mythread2.RunLoop));
backThread.Name = "后台线程";
backThread.IsBackground = true;
backThread.Start();
frontThread.Start();
}
class MyThread
{
public int Count {
get; set; }
public void RunLoop()
{
string threadName = Thread.CurrentThread.Name;
for(int i = 0; i < Count; i++)
{
Console.WriteLine("{0} Count : {1}", threadName, i);
}
}
}
可以看到,后台程序没有执行完,程序就结束运行了。当我们把isBackground注释掉(默认的都为前台进程)。
程序需要等待所有前台线程执行完才结束。后台线程一般用于处理不重要的事情,应用程序结束时,后台线程是否执行完成对整个应用程序没有影响。如果要执行的事情很重要,需要将线程设置为前台线程。
线程类的方法
方法名 | 功能 |
---|---|
Abort() | 终止本线程 |
GetDomain() | 返回当前线程正在其中运行的当前域。 |
GetDomainId() | 返回当前线程正在其中运行的当前域Id。 |
Interrupt() | 中断处于 WaitSleepJoin 线程状态的线程。 |
Join() | 已重载。 阻塞调用线程,直到某个线程终止时为止。 |
Resume() | 继续运行已挂起的线程。 |
Start() | 执行本线程。 |
Suspend() | 挂起当前线程,如果当前线程已属于挂起状态则此不起作用 |
Sleep() | 把正在运行的线程挂起一段时间。 |
下面对Interrupt函数进行代码演示。
sleeper = new Thread(new ThreadStart(SleepThread));
interrupter = new Thread(new ThreadStart(InterruptThread));
sleeper.Start();
interrupter.Start();
}
public static Thread sleeper;
public static Thread interrupter;
static public void SleepThread()
{
for(int i = 0; i < 50; i++