自从接触代码以来,多线程一直困扰着小弟。不理解什么是多线程,不理解怎么用多线程,不理解什么时候用多线程???这里仅仅是学习多线程的笔记,以供自己闲暇时间翻看。
1)首先列举学习多线程所必须明白的基本概念:
进程(Process)是Windows系统中的一个基本概念,它包含着一个运行程序所需要的资源。进程之间是相对独立的,一个进程无法访问另一个进程的数据(除非利用分布式计算方式),一个进程运行的失败也不会影响其他进程的运行,Windows系统就是利用进程把工作划分为多个独立的区域的。进程可以理解为一个程序的基本边界。当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源。而一个进程又是由多个线程所组成的。
线程(Thread)是进程中的基本执行单元,在进程入口执行的第一个线程被视为这个进程的主线程。在.NET应用程序中,都是以Main()方法作为入口的,当调用此方法时系统就会自动创建一个主线程。线程主要是由CPU寄存器、调用栈和线程本地存储器(Thread Local Storage,TLS)组成的。CPU寄存器主要记录当前所执行线程的状态,调用栈主要用于维护线程所调用到的内存与数据,TLS主要用于存放线程的状态信息。线程,有时被称为轻量级进程,是程序执行流的最小单元。
应用程序域(AppDomain)是一个程序运行的逻辑区域,它可以视为一个轻量级的进程,.NET的程序集正是在应用程序域中运行的,一个进程可以包含有多个应用程序域,一个应用程序域也可以包含多个程序集。在一个应用程序域中包含了一个或多个上下文context,使用上下文CLR就能够把某些特殊对象的状态放置在不同容器当中。
多线程:线程是程序中一个单一的顺序控制流程。在单个程序中同时运行多个线程完成不同的工作,称为多线程。
2)基础知识:
1>公共属性:
CurrentContext | 获取线程正在其中执行的当前上下文。 |
CurrentThread | 获取当前正在运行的线程。 |
ExecutionContext | 获取一个 ExecutionContext 对象,该对象包含有关当前线程的各种上下文的信息。 |
IsAlive | 获取一个值,该值指示当前线程的执行状态。 |
IsBackground | 获取或设置一个值,该值指示某个线程是否为后台线程。 |
IsThreadPoolThread | 获取一个值,该值指示线程是否属于托管线程池。 |
ManagedThreadId | 获取当前托管线程的唯一标识符。 |
Name | 获取或设置线程的名称。 |
Priority | 获取或设置一个值,该值指示线程的调度优先级。 |
ThreadState | 获取一个值,该值包含当前线程的状态。 |
2>多线程的优先级:
成员名称 | 说明 |
---|---|
Lowest | 可以将 Thread 安排在具有任何其他优先级的线程之后。 |
BelowNormal | 可以将 Thread 安排在具有 Normal 优先级的线程之后,在具有 Lowest 优先级的线程之前。 |
Normal | 默认选择。可以将 Thread 安排在具有 AboveNormal 优先级的线程之后,在具有BelowNormal 优先级的线程之前。 |
AboveNormal | 可以将 Thread 安排在具有 Highest 优先级的线程之后,在具有 Normal 优先级的线程之前。 |
Highest | 可以将 Thread 安排在具有任何其他优先级的线程之前。 |
3>System.Threading.Thread的方法
方法名称 | 说明 |
---|---|
Abort() | 终止本线程。 |
GetDomain() | 返回当前线程正在其中运行的当前域。 |
GetDomainId() | 返回当前线程正在其中运行的当前域Id。 |
Interrupt() | 中断处于 WaitSleepJoin 线程状态的线程。 |
Join() | 已重载。 阻塞调用线程,直到某个线程终止时为止。 |
Resume() | 继续运行已挂起的线程。 |
Start() | 执行本线程。 |
Suspend() | 挂起当前线程,如果当前线程已属于挂起状态则此不起作用 |
Sleep() | 把正在运行的线程挂起一段时间。 |
3)简单示例:
(1)委托绑定方法不带参数:
public class Message { public void ShowMessage() { string message = string.Format("Async threadId is :{0}", Thread.CurrentThread.ManagedThreadId); Console.WriteLine(message); for (int n = 0; n < 20; n++) { Thread.Sleep(300); Console.WriteLine("The number is:" + n.ToString()); } } } class Program { static void Main(string[] args) { Console.WriteLine("Main threadId is:" + Thread.CurrentThread.ManagedThreadId); Message message = new Message(); Thread thread = new Thread(new ThreadStart(message.ShowMessage)); thread.IsBackground = true;//设置为后台线程 thread.Start(); Console.WriteLine("Do something ..........!"); Console.WriteLine("Main thread working is complete!"); } }
示例分析:
1>在主线程内创建一个显示信息的线程thread。定义线程,使用ThreadStart委托绑定ShowMessage方法,最后完成整个进程。
2>IsBackground:设置线程是否为后台线程。本例中设置为true,则系统不会执行thread线程而直接结束进程。
3>使用Thread.Start()启动的线程默认为前台线程,而系统必须等待所有前台线程运行结束后,应用程序域才会自动卸载。曾经介绍过线程Thread有一个属性IsBackground,通过把此属性设置为true,就可以把线程设置为后台线程!这时应用程序域将在主线程完成时就被卸载,而不会等待异步线程的运行。
(2)委托绑定方法带参数:
public class Person { public string Name { get; set; } public int Age { get; set; } } public class Message { public void ShowMessage(object person) { if (person != null) { Person _person = (Person)person; string message = string.Format("\n{0}‘s age is {1}!\nAsync threadId is:{2}",_person.Name, _person.Age, Thread.CurrentThread.ManagedThreadId); Console.WriteLine(message); } for (int n = 0; n < 10; n++) { Thread.Sleep(300); Console.WriteLine("The number is:" + n.ToString()); } } } class Program { static void Main(string[] args) { Console.WriteLine("Main threadId is:" + Thread.CurrentThread.ManagedThreadId); Message message = new Message(); //绑定带参数的异步方法 Thread thread = new Thread(new ParameterizedThreadStart(message.ShowMessage)); Person person = new Person(); person.Name = "Jack"; person.Age = 21; thread.IsBackground = true; thread.Start(person); //启动异步线程 Console.WriteLine("Do something ..........!"); Console.WriteLine("Main thread working is complete!"); //Thread.Sleep(5000);//为了等待其他后台线程完成后再结束主线程,就可以使用Thread.Sleep()方法。 thread.Join();//thread.Join() 就能保证主线程在异步线程thread运行结束后才会终止 } }
示例分析:
1>ParameterizedThreadStart支持绑定带参数的方法。
2>本例将thread设置为后台线程,thread.Join();//thread.Join() 就能保证主线程在异步线程thread运行结束后才会终止。
(3)启动和终止线程
class Program { static void Main(string[] args) { Console.WriteLine("Main threadId is:" + Thread.CurrentThread.ManagedThreadId); Thread thread = new Thread(new ThreadStart(AsyncThread)); thread.IsBackground = true; thread.Start(); thread.Join(); } //以异步方式调用 static void AsyncThread() { try { string message = string.Format("\nAsync threadId is:{0}",Thread.CurrentThread.ManagedThreadId); Console.WriteLine(message); for (int n = 0; n < 10; n++) { //当n等于4时,终止线程 if (n >= 4) { Thread.CurrentThread.Abort(n); } Thread.Sleep(300); Console.WriteLine("The number is:" + n.ToString()); } } catch (ThreadAbortException ex) { //输出终止线程时n的值 if (ex.ExceptionState != null) Console.WriteLine(string.Format("Thread abort when the number is: {0}!", ex.ExceptionState.ToString())); //取消终止,继续执行线程 Thread.ResetAbort(); Console.WriteLine("Thread ResetAbort!"); } Console.WriteLine("Thread Close!"); } }
示例分析:
1>若想终止正在运行的线程,可以使用Abort()方法。在使用Abort()的时候,将引发一个特殊异常 ThreadAbortException 。若想在线程终止前恢复线程的执行,可以在捕获异常后 ,catch(ThreadAbortException ex){...} 中调用Thread.ResetAbort()取消终止。而使用Thread.Join()可以保证应用程序域等待异步线程结束后才终止运行。
2>Thread.Suspend()与 Thread.Resume()是在Framework1.0 就已经存在的老方法了,它们分别可以挂起、恢复线程。但在Framework2.0中就已经明确排斥这两个方法。这是因为一旦某个线程占用了已有的资源,再使用Suspend()使线程长期处于挂起状态,当在其他线程调用这些资源的时候就会引起死锁!所以在没有必要的情况下应该避免使用这两个方法。
4)线程池
使用ThreadStart与ParameterizedThreadStart建立新线程非常简单,但通过此方法建立的线程难于管理,若建立过多的线程反而会影响系统的性能。所以.NET引入CLR线程池这个概念。CLR线程池并不会在CLR初始化的时候立刻建立线程,而是在应用程序要创建线程来执行任务时,线程池才初始化一个线程。线程的初始化与其他的线程一样。在完成任务以后,该线程不会自行销毁,而是以挂起的状态返回到线程池。直到应用程序再次向线程池发出请求时,线程池里挂起的线程就会再度激活执行任务。这样既节省了建立线程所造成的性能损耗,也可以让多个任务反复重用同一线程,从而在应用程序生存期内节约大量开销。
不带参数:
class Program { static void Main(string[] args) { //把CLR线程池的最大值设置为1000 ThreadPool.SetMaxThreads(1000, 1000); //显示主线程启动时线程池信息 //启动工作者线程 ThreadPool.QueueUserWorkItem(new WaitCallback(AsyncCallback)); Console.ReadKey(); } static void AsyncCallback(object state) { Thread.Sleep(200); ThreadMessage("AsyncCallback"); Console.WriteLine("Async thread do work!"); } }
带参数:
class Program { static void Main(string[] args) { //把线程池的最大值设置为1000 ThreadPool.SetMaxThreads(1000, 1000); ThreadPool.QueueUserWorkItem(new WaitCallback(AsyncCallback), "Hello Elva"); Console.ReadKey(); } static void AsyncCallback(object state) { Thread.Sleep(200); ThreadMessage("AsyncCallback"); string data = (string)state; Console.WriteLine("Async thread do work!\n" + data); } }
5)利用BeginInvoke和EndInvoke完成异步委托
class Program { delegate string MyDelegate(string name); static void Main(string[] args) { MyDelegate mDelegate = new MyDelegate(Method); IAsyncResult result = mDelegate.BeginInvoke("lesli", null, null);//异步调用委托,获取计算结果 while (!result.IsCompleted) { Thread.Sleep(200); Console.WriteLine("Main thread do work"); } string data = mDelegate.EndInvoke(result);//等待异步方法完成,调用EndInvoke(IAsyncResult)获取运行结果 Console.WriteLine(data); Console.Read(); } static string Method(string name) { ThreadMessage("Async Thread"); Thread.Sleep(2000); return "Method " + name; } }
示例分析:
建立一个委托对象,通过IAsyncResult BeginInvoke(string name,AsyncCallback callback,object state) 异步调用委托方法,BeginInvoke 方法除最后的两个参数外,其它参数都是与方法参数相对应的。通过 BeginInvoke 方法将返回一个实现了 System.IAsyncResult 接口的对象,之后就可以利用EndInvoke(IAsyncResult ) 方法就可以结束异步操作,获取委托的运行结果。
6)多线程中的回调函数:
不传递参数:
class Program { public delegate string MyDelegate(string name); static void Main(string[] args) { ThreadMessage("Main Thread"); MyDelegate mDelegate = new MyDelegate(Method); mDelegate.BeginInvoke("lili", new AsyncCallback(Completed), null); for (int i = 0; i < 10; i++) { Console.WriteLine("Main thread do work {0}", i); } Console.Read(); } static void Completed(IAsyncResult ar) { ThreadMessage("Async Completed"); AsyncResult result = (AsyncResult)ar; MyDelegate mDelegate2 = (MyDelegate)result.AsyncDelegate; string data = mDelegate2.EndInvoke(result); Console.WriteLine(data); } static string Method(string name) { ThreadMessage("Async Thread"); Thread.Sleep(2000); return "Method " + name; } //显示线程现状 static void ThreadMessage(string data) { string message = string.Format("{0}\n CurrentThreadId is {1}", data, Thread.CurrentThread.ManagedThreadId); Console.WriteLine(message); } }
传递参数:
class Program { public class Person { public string Name; public int Age; } delegate string MyDelegate(string name); static void Main(string[] args) { ThreadMessage("Main Thread"); //建立委托 MyDelegate myDelegate = new MyDelegate(Method); //建立Person对象 Person person = new Person(); person.Name = "Elva"; person.Age = 27; //异步调用委托,输入参数对象person, 获取计算结果 myDelegate.BeginInvoke("Leslie", new AsyncCallback(Completed), person); //在启动异步线程后,主线程可以继续工作而不需要等待 for (int n = 0; n < 6; n++) Console.WriteLine(" Main thread do work!"); Console.WriteLine(""); Console.ReadKey(); } static string Method(string name) { ThreadMessage("Async Thread"); Thread.Sleep(2000); return "\nHello " + name; } static void Completed(IAsyncResult result) { ThreadMessage("Async Completed"); //获取委托对象,调用EndInvoke方法获取运行结果 AsyncResult _result = (AsyncResult)result; MyDelegate myDelegate = (MyDelegate)_result.AsyncDelegate; string data = myDelegate.EndInvoke(_result); //获取Person对象 Person person = (Person)result.AsyncState; string message = person.Name + "‘s age is " + person.Age.ToString(); Console.WriteLine(data + "\n" + message); } static void ThreadMessage(string data) { string message = string.Format("{0}\n ThreadId is:{1}", data, Thread.CurrentThread.ManagedThreadId); Console.WriteLine(message); } }
7)示例:socket多线程中的异步操作
server端:
class Program { static void Main(string[] args) { ThreadPool.SetMaxThreads(1000, 1000); IPAddress ip = IPAddress.Parse("127.0.0.1"); TcpListener tcpListener = new TcpListener(ip, 5000); tcpListener.Start(); while (true) { ChatClient chatClient = new ChatClient(tcpListener.AcceptTcpClient()); } } } public class ChatClient { static TcpClient tcpClient; static byte[] byteMessage; static string clientEndPoint; public ChatClient(TcpClient tcpClient1) { tcpClient = tcpClient1; byteMessage = new byte[tcpClient.ReceiveBufferSize]; clientEndPoint = tcpClient.Client.RemoteEndPoint.ToString(); Console.WriteLine("Client‘s endpoint is " + clientEndPoint); NetworkStream networkStream = tcpClient.GetStream(); networkStream.BeginRead(byteMessage, 0, tcpClient.ReceiveBufferSize, new AsyncCallback(ReceiveCallback), null); } public void ReceiveCallback(IAsyncResult ar) { Thread.Sleep(100); ThreadPoolMessage("\n Message is receiving"); NetworkStream networkStream2 = tcpClient.GetStream(); int length = networkStream2.EndRead(ar); if (length < 1) { tcpClient.GetStream().Close(); throw new Exception("Disconnection"); } string message = Encoding.UTF8.GetString(byteMessage, 0, length); Console.WriteLine("Message:" + message); byte[] sendMessage = Encoding.UTF8.GetBytes("Message is received"); NetworkStream networkStreamWrite = tcpClient.GetStream(); networkStreamWrite.BeginWrite(sendMessage, 0, sendMessage.Length, new AsyncCallback(SendCallback), null); } public void SendCallback(IAsyncResult ar) { Thread.Sleep(100); ThreadPoolMessage("\n Message is sending"); tcpClient.GetStream().EndWrite(ar); tcpClient.GetStream().BeginRead(byteMessage, 0, tcpClient.ReceiveBufferSize, new AsyncCallback(ReceiveCallback), null); } //显示线程池现状 static void ThreadPoolMessage(string data) { int a, b; ThreadPool.GetAvailableThreads(out a, out b); string message = string.Format("{0}\n CurrentThreadId is {1}\n " + "WorkerThreads is:{2} CompletionPortThreads is :{3}\n", data, Thread.CurrentThread.ManagedThreadId, a.ToString(), b.ToString()); Console.WriteLine(message); } }
client端:
class Program { static void Main(string[] args) { TcpClient tcpClient = new TcpClient("127.0.0.1", 5000); NetworkStream networkStream = tcpClient.GetStream(); byte[] sendMessage = Encoding.UTF8.GetBytes("Client request connection"); networkStream.Write(sendMessage, 0, sendMessage.Length); networkStream.Flush(); byte[] receiveMessage = new byte[1024]; int count = networkStream.Read(receiveMessage, 0, 1024); Console.WriteLine(Encoding.UTF8.GetString(receiveMessage)); Console.ReadKey(); } }
8)优缺点:
优点:多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。线程是在同一时间需要完成多项任务的时候实现的。使用线程可以把耗时比较长的任务放到后台单独开一个线程,使程序运行得更快。同时使用多线程可以开发出更人性化的界面,例如当我们提交某项数据的时候通过使用多线程显示处理进度等效果。最简单的比喻多线程就像一个工厂的工人,而进程则是工厂的某个车间。工人离开车间就无法生产,同理车间也不可能只有一个员工。多线程的出现就是为了提高效率。
缺点:更过的线程意味着更多的内存消耗;线程的退出可能会对程序带来麻烦;处理不当造成更多的死锁;过多的线程会影响性能(因为操作系统需要在各个线程间切换)。
说到死锁,这里简单介绍下:
什么叫做死锁?
多个线程在执行过程中因争夺资源而造成的一种僵局,若无外力作用,将无法向前推进。
产生死锁的原因
1> 竞争资源。当系统*多个线程共享的资源如打印机等,其数目不足以满足诸线程的需要时,会引起诸线程对资源的竞争而产生死锁;
2> 线程间推进顺序非法,线程在运行过程中,请求和释放资源的顺序不当,也同样会导致产生进程死锁。
产生死锁的必要条件
1> 互斥条件,即一段时间某资源只由一个线程占用;
2> 请求和保持条件,指进程已经保持了至少一个资源,但又提出了新的资源请求新的资源求情,而该资源又已经被其他线程占有,此时请求进程序阻塞,但又对自己已经获得的其他资源保持不放;
3> 不剥夺条件,指线程已经获得的资源,在未使用完之前,不能被剥夺,只能在使用完时自己释放;
4> 环路等待,指在发生死锁时,必然存在一个进程—资源的环形链。如P1等待一个P2占用的资源,P2正在等待P3占用的资源,P3正在等待P1占用的资源。
死锁的解除方式
1>剥夺资源,从其他进程剥夺足够数量的资源给死锁进程,以解除死锁状态;
2>撤销进程,最简单的撤销进程的方法是使全部死锁进程都夭折掉,稍微温和一点的方法是按照某种顺序逐个撤销进程,直至有足够的资源可用,使死锁状态消除为止。