版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、线程、多线程基本知识C#线程基础知识相关概念进程(process):进程就是当前运行的应用 程序 .进程由一个或多个线程以及程序在内存中 的代码、数据和其他资源组成。 程序资源通常有 打开的文件、信号灯和动态分配的内存。线程 (thread): 线程是操作系统分配处理器 时间的基本单元 .每个线程都维护异常处理程 序、调度优先级和一组系统用于在调度该线程前 保存线程上下文的结构。 线程上下文包括为使线 程在线程的宿主进程地址空间中无缝地继续执 行所需的所有信息, 包括线程的 CPU 寄存器组 和堆栈。dotnet framework 中的两个线程类 : 托管线 程( System.Thread
2、ing.Thread ); 操作系统线程 (System.Diagnostics.ProcessThread).在托管环境中 ,对线程的管理都是通过 Thread 类 来完成的 .Thread & ThreadPool 的一些背景知识CLR 目 前 的 多 线 程 技 术 依 然 是Windows 操作系统所提供的,不过 .NET CLR 开发小组似乎保留了将其分离的权利。 在某些环 境,CLR线程并不会直接映射到一个 Windows 线程上,他们可能会用 Windows fiber 来代替, 以期获得更好的执行性能。 未来的 CLR 版本甚 至会直接用某个已存在的空闲线程来代替 new Th
3、read() 执行其他任务。 CLR 线程使用 了 Windows 线程开发的很多技巧, 这样我们可 以使用简单的代码来处理原本需要花费很多精 力才能完成的工作。 这种包装分离带来的另外一 个好处就是,我们无需改动我们的代 码就能获 得 CLR 和操作系统升级带来的性能提升。除了直接使用 CLR 线程,我们还可以使用 Thread.BeginThreadAffinity() 等手段直接使用 操作系统级别的线程, 只不过要记得调用相关方 法去 End 。早 期的 DOS 和 Windows 16-bit 都是单线程 操作系统,这种操作系统上的某个进程一旦陷入 死循环, 整个操作系统都完蛋, 你能
4、做的只有重 启计算机。 Windows NT 3.1 是微软开发的第一 个支持多线程功能的操作系统, 这从某种程度上 可以说是 Windows 成为 健壮性 操作系统 的标志。 在支持多线程的操作系统里, 每个进程 都拥有自己的线程, 也就是说理论上不会陷入上 述那样的尴尬状况了。 死循环的线程被冻结, 而 其他线程依 旧能正常运转,用户也就有机会强 行结束那个死掉的家伙。严格来说, 线程是一笔昂贵的开支。 创建线程并 不简单,首先得分配并初始化一个线程 内核对 象 (thead kernel object) ,并为这个线程保留 1MB user-mode stack 和 12KB 以 上 的
5、 kernel-mode stack 。在完成这些之后,线程才被 创建。 Windows 会发送消息通知目标进程以及 其所有 DLL 线程可用。而销毁线程同样需要发 送消息通知,最后还得释放所有的保留空间。在单 CPU 计算机上,任何时候都只有一个线程 在执行。 Windows 保持线程对象状态,并决定 接下来哪个线程会被执行。 每个线程每次大概可 以获得 20 毫秒的 CPU 执行时间片, 然后切换 执行另外一个线程。 这个过程有个专业术语叫 线程上下文切换 (context switch) 。操作系统需 要花费相当代价才能走完一次切换:进入内核模式。将 CPU 寄存器信息保存到当前正在执行
6、 的线程内核对象。获取一个 Spinlock ,按计划决定下一个要执 行的线程,然后释放 Spinlock 。如果下一个线 程属于其他的进程, 那么我们还得为虚拟地址交 换付出更多代价。从要被执行的线程内核对象载入 CPU 寄离开内核模式。所有这些操作可能导致操作系统和应用程序比 单线程操作系统执行得更慢,但这些都是值得 的,芯片生产商带来的超线程 (hyper-threading) 和多核 (mulit-core) CPU 为多线程提供了真正 的舞台,每个内核上都可以真正并发执行一个线 程。超线程 CPU 包含两个逻辑内核, 每个逻辑 内核都拥有自己的寄存器,只是它们需要共享 CPU 缓存等
7、资源。当一个逻辑 CPU 因某种原 因被暂停,芯片会切换到另外一个逻辑 CPU 继 续执行任务, 超线程芯片能带来 10% - 30% 左 右的性能提升。而像 Pentium D 、 Athlon 64 X2 这类真正的多核 CPU 芯片,它们集成了多个真 正意义上的物理内核, 每个内核都有自己的完整 的寄存器和缓存,这才是 100% 的性能提升。 现在某些服务器用的芯片会同时使用多核和超 线程技术,因此你可能在任务管理器中看到 4 个或 8 个 CPU 显示。芯片发展已经从单纯的 主频提升转移到多核集成上来, 不久我们就可以 使用 4 核、 8 核,甚至是更多更强大的多核处 理器。创建和销
8、毁线程代价不菲,过多的线程会消耗 掉大量的内存和 CPU 资源。为了改善这种状 况, CLR 提供了一种称之为 线程池 (thread pool) 的技术。直观来说,线程池就是为应用 程序提供的一堆可用线程集合, 线程池在进程所 有应用程序域 (AppDomain) 间共享。在 CLR 初始化之初,线程池内是没有任何线程 的,其内部有一个专门存储请求的队列。 当应用 程序试图执行异步等操作时, 这些方法调用会被 包装并加入到线程池队列中。 线程池从队列提 取任务请求, 并为其分配可用线程。 如果线程池 内没有可用线程,那么一个新的线程会被创建。 线程完成任务执行后,并不会被摧毁。相反,它 被放
9、 回到线程池中,然后等待被分配给其他任 务。线程池会尝试用同一个线程来处理应用程序 的多个任务请求。 而一旦应用程序在极短时间内 发出多个请求,那么它会尝 试创建额外的线程 来分配队列中的任务, 这有可能导致池内线程数 量急剧增加, 同时耗费大量的系统资源。 当请求 完成,池内多余的线程会在空闲 2 分钟后被释 放并回收相关资源,直到某个最小线程阀值设 置。当任务请求超出最小阀值设置, 线程池并不会立 即创建新线程,而是等待大约 500 毫秒左右。 这么做的目的是看看在这段时间内是否有其他 工作线程完成任务来接手这个请求, 这样就可以 避免创建新线程的消耗。最小线程阀值设置 (ThreadPo
10、ol.SetMinThreads) 最 好 不 要 小 于 CPU 内核数量,否则会导致性能问题。在线程池内部,它包含两种 类型的线程,分别 是 worker thread 和 I/O threads 。工作线程用 来处理 compute-bound 异步操作 ( 包括初始化 I/O-bound 操作 ),而 I/O 线程则用于异步执行 诸如文件访问、网络通讯、数据库操作、 WebService 调用以及某些硬件设备控制等。 CLR 允许开发人员设置线程池的最大线程数 量,并确保池内线程数量不会超出这个设置。 CLR 2.0 ThreadPool 默认为每个 CPU 处理器 提供 25 个工作
11、线程以及 1000 个 I/O 线程, 通常情况下这已经足够了, 并不需要我们做出特 别的处理。摘自 MSDN操作系统使用进程将它们正在执行的不同应用 程序分开。线程是操作系统分配处理器时间的基 本单元,并且该进程中可以有多个线程同时执行 代码。每个线程都维护异常处理程序、 调度优先 级和一组系统用于在调度该线程前保存线程上 下文的结构。线程上下文包括为使线程在线程的 宿主进程地址空间中无缝地继续执行所需的所 有信息,包括线程的 CPU 寄存器组和堆栈。.NET Framework 将操作系统进程进一步细分 为由 System.AppDomain 表示的、称为应用程 序域的轻量托管子进程。一个
12、或多个托管线程 (由 System.Threading.Thread 表 示)可以在 同一个非托管进程中的一个或任意数目的应用 程序域中运行。虽然每个应用程序域都是用单个 线程启动的,但该应用程序域中的代码可以创建 附加应用程 序域和附加线程。其结果是托管线 程可以在同一个非托管进程中的应用程序域之 间自由移动;您可能只有一个线程在若干应用程 序域之间移动。支 持抢先多任务处理的操作系统可以创建多个 进程中的多个线程同时执行的效果。 它通过以下 方式实现这一点: 在需要处理器时间的线程之间 分割可用处理器时间,并 轮流为每个线程分配 处理器时间片。 当前执行的线程在其时间片结束 时被挂起, 而
13、另一个线程继续运行。 当系统从一 个线程切换到另一个线程时, 它将保存被抢先的 线程的线程上下文, 并重新加载线程队列中下一 个线程的已保存线程上下文。时间片的长度取决于操作系统和处理器。 由于每 个时间片都很小, 因此即使只有一个处理器, 多 个线程看起来似乎是在同时执行。 这实际上就是 多处理器系统中发生的情形, 在此类系统中, 可 执行线程分布在多个可用处理器中。何时使用多个线程 需要用户交互的软件必须尽可能快地对用户的 活动作出反应,以便提供丰富多彩的用户体验。 但同时它必须执行必要的计算以便尽可能快地 将数据呈现给用户。 如果应用程序仅使用一个执 行线程,则可以将异步编程与 .NET
14、 远程处理 或使用 ASP.NET 创建的 XML Web services 结合使用,在使用自己的计算机的处理时间之外 还使用其他计算机的处理时间, 从而提高对用户 的响应速度并减少应用程序的数据处理时间。 如 果您正在进行大量的输入 /输出工作,则还可以 使用 I/O 完成端口来提高应用程序的响应速多个线程的优点无 论如何,要提高对用户的响应速度并且处理 所需数据以便几乎同时完成工作, 使用多个线程 是一种最为强大的技术。 在具有一个处理器的计 算机上,多个线程可以通 过利用用户事件之间 很小的时间段在后台处理数据来达到这种效果。 例如,在另一个线程正在重新计算同一应用程序 中的电子表格的
15、其他部分时, 用户可以编辑该电 子表格。无需修改,同一个应用程序在具有多个处理器的 计算机上运行时将极大地满足用户的需要。 单个 应用程序域可以使用多个线程来完成以下任务:通过网络与 Web 服务器和数据库进行通信。执行占用大量时间的操作。 区分具有不同优先级的任务。 例如, 高优先级线 程管理时间关键的任务, 低优先级线程执行其他 任务。 使用户界面可以在将时间分配给后台任务时仍能快速作出响应。多个线程的缺点 建议您使用尽可能少的线程, 这样可以最大限度 地减少操作系统资源的使用, 并可提高性能。 线 程处理还具有在设计应用程序时要考虑的资源 要求和潜在冲突。这些资源要求如下所述:系统将为进
16、程、 AppDomain 对象和线程所需的 上下文信息使用内存。因此,可以创建的进程、 AppDomain 对象和线程的数目会受到可用内 存的限制。跟踪大量的线程将占用大量的处理器时间。 如果 线程过多,则其中大多数线程都不会产生明显的 进度。如果大多数当前线程处于一个进程中, 则 其他进程中的线程的调度频率就会很低。 使用许多线程控制代码执行非常复杂, 并可能产 生许多错误。销毁线程需要了解可能发生的问题并对那些问 题进行处理。提 供对资源的共享访问会造成冲突。为了避免 冲突,必须对共享资源进行同步或控制对共享资 源的访问。如果在相同或不同的应用程序域中未 能正确地使访问同步, 则 会导致出
17、现一些问题, 这些问题包括死锁和争用条件等, 其中死锁是指 两个线程都停止响应,并且都在等待对方完成; 争用条件是指由于意外地出现对两个事件的执 行时间的临界依赖性而发生反常的结果。 系统提 供了可用于协调多个线程之间的资源共享的同 步对象。减少线程的数目使同步资源更为容易。需要同步的资源包括:系统资源(如通信端口) 。 多个进程所共享的资源(如文件句柄) 。 由多个线程访问的单个应用程序域的资源 (如全 局、静态和实例字段) 。一个另类的例子丰富的用户体验是所有交互式应用程序的主要 方面。需要用户交互的软件必须尽可能快地响应 用户的活动。 与此同时, 应用程序必须能够处理 数据以便将结果显示
18、给用户。在应用程序中使用多个线程可以将用户界面 (UI) 执行的任务与后台执行的任务分开。通过 此方式组织任务, 可以由 UI 响应用户输入, 同 时由后台进程进行数据处理。创建辅助线程 应用程序可以创建一个或多个线程以执行方法。 创建辅助(或从属)线程的第一个步骤是创建 ThreadStart 代理,指定要由该线程执行的方 法。然后将 ThreadStart 代理传递给 Thread 类的构造函数。例如,要启动新的线程并执行 MyFunction 方法,请调用 Thread 类的 Start 方法,如下所示:ThreadStart starter = new ThreadStart(MyFu
19、nction);Thread t = new Thread(starter); t.Start();从辅助线程更新用户界面 您可以使用 Control.Invoke 从 UI 线程以外的 其他线程更新用户界面 (UI) 。此方法在 UI 线 程 上 的 控 件 线 程 上 下 文 中 执 行 代 理 。 .NET Framework 精简版只支持 .NET Framework 完 整 版 中 的 重 载 Control.Invoke 方 法 。 Control.Invoke 只使用一个参数:一个指定在 UI 线程上执行哪个方法的代理。该代理的类型 必须为 EventHandler ,并且具有以
20、下签名:void MyFunctionName(object sender, EventArgs e)需要注意的一点是,如果要在辅助线程中更新UI , 就 必 须 在 代 码 中 调 用 Application.DoEvents() 。 调 用 Application.DoEvents() 可确保辅助线程激发的 任何事件都由该 UI 线程处理。下面的示例代码说明如何创建辅助线程, 然后从 UI 线 程 和 辅 助 线 程 更 新 名 为 listBox1 的 ListBox 控件:本 文 来 自 CSDN 博 客 , 转 载 请 标 明 出 处 : HYPERLINK http:/blog.c
21、sd http:/blog.csd n.n et/hua ngzhim in g1278/archive/2009/03/30/4036513.aspx多线程基础序:我是自学的c#,在看到多线程一章时,郁闷了,搞不懂,很难理 解吧自认为有软件天赋,却没看懂 唉自信心被打击了,到网上查了很 多的资料,包括MSDN上也查过资料,可惜还是没搞懂多线程于是,硬着头皮一遍一遍的看多线程那一章,终于在看完第31 遍,我写出了第一个多线程程序,还算小有所获,鉴于对网上很多资料没办法理 解(因为,很多资料一来就大篇大篇的代码,让人头晕,我是这么觉得)所以,我自己就写了这篇心得体会,希望能给 大家带来些许帮助.
22、匆忙之中,错误难免,欢迎指正,共同进步.正文:首先我要提一点,关于线程的基础知识,一个程序,即一个进 程,可以有很多个线程,当然,至少要有一个线程,即主线程.相信大家都知道多 线程的好处吧,举个书上的例子吧,Windows在复制文件的时候,有个动画,是在复制文件过程中进行的,也就是 一边复制文件,一边播放动画,这个就是很简单的多线程,如果没有动画,复制一 个大文件的时候,我们知道计算机是死机了,还是仍然在复制呢??多线程就很好的解决了这个问题.懂 了吧,恩,很好!那么,我们就开始吧!首先,我们写个简单的单线程程序,也就是只有程序自己创建 的那个主线程,没有使用多线程.创建一个新工程,向窗口添加
23、一个label命名为Iabel1;我们要让程序运行时 label1就显示一个数字,假设为100;通常我们会直接在窗口加载事件中写 label1.Text = 100; 这样,运行public Form1()public Form1(),label1 果然显示了 100;代码如下 :( 例 1)using System;using System.Windows.Forms;namespaceThreadTest public partial class Form1 : Form public Form1() InitializeComponent();private void Form1_Loa
24、d( object sender, EventArgs e) label1.Text = 100;很简单吧 ,看懂了吗 ?什么 , 没有, 啊神啊 救救我吧 , 那请你在翻书 , 把最最最最最基础的书翻出 来看看里面的最最最最最简单例子 ( 以后不要说我认识你 )好了,看懂的朋友继续往下看:我们现在要将程序稍稍改动一下,添加一个 Button, 命名为 button1, 我们要在 按下 button1 后,将 lable1 的 text 从 0 显示到 100,那么,我们需要添加 button1 的 Click 事件 , 在 click 事件内写入循环显示 0 到 100.代码如下 :( 例
25、2)using System;using System.Windows.Forms;namespaceThreadTest public partial class Form1 : Form public Form1()public Form1()InitializeComponent();private void Form1_Load( object sender, EventArgs e) label1.Text = 0;private void button1_Click( object sender, EventArgs e) for (int i=0;i101;i+)label1.T
26、ext = i.ToString(); 运行一下看看 , 按一下 button1 ,结果是我们一下就看到了 100,并没有看到 0100 的过程,为什么呢? 呵呵,因为你的处理器速度太快了,就只能看到最后的结果,那么,怎样才能 看到中间过程呢? ( 等一下再讲 ) 我们先用函数的方式来实现上面的功能写个名为 run 的函数吧: private void run()for (int i=0;i101;i+)label1.Text = i.ToString();这样就可以直接调用 run 函数实现功能了,而不用在事件函数内写代码。 ( 这样 做是有好处的 )整个代码如下: ( 例 3) using
27、 System;using System.Windows.Forms;namespaceThreadTestpublic partial class Form1 : Form InitializeComponent();private void Form1_Load( object sender, EventArgs e) label1.Text = 0;private void button1_Click( object sender, EventArgs e) run(); / 调用 run 函数private void run()for (int i=0;i 100)timer1.Sto
28、p();label1.Text = i.ToString();同样的,我们运行一下,看看结果,很好,我们能够看到 0100 循环的过程了。下面我们就要进入多线程了,不知道各位将上面的内容看懂了没有? 开始进入多线程之前我还是先简单的说说定义线程吧。 ( 与多线程有关的其它内容我就不说了吧,那个太多太多了 )由于要使用多线程, 我们需要引用 System.Threading; 所以之后的代码都会在前 面加上 using System.Threading;怎么定义线程呢?通过下面的语句就定义一个名为 thread1 的线程private Thread thread1;和定义函数极为相似定义线程之后
29、,就要进行实例化:thread1 = new Thread( new ThreadStart (run); 这个语句的意思就是实例化 thread1 并将 run 函数设定为 thread1 的入口函数 ( 大概意思就是,让 run 函数在线程 thread1 上执行 , 我是这样理解的 )创建线程就算完成了,那么怎么运行线程呢?其实和启动 timer1 是类似的, thread1.Start(); 就运行了我们创建的线程 thread1 。好了,大功告成!哈哈,别着急,既然我们创建了线程,那么在关闭窗口的时 候,就要撤消线程。添加 FormClosing 事件,在事件内部写如撤消线程的代码:
30、private void Form1_FormClosing( object sender, FormClosingEventArgs e) if (thread1.IsAlive) / 判断 thread1 是否存在,不能撤消一个不存 在的线程,否则会引发异常thread1.Abort(); / 撤消 thread1这样才算大功告成嘛,整理的代码如下: (例 5)( 在例 3的基础上加以改动 )using System;using System.Threading;using System.Windows.Forms;namespaceThreadTestpublic partial cla
31、ssForm1 : Formprivate Thread thread1;public Form1() InitializeComponent();private void Form1_Load( object sender, EventArgs e) label1.Text = 0; private void button1_Click( object sender, EventArgs e)thread1 = new Thread( new ThreadStart (run); thread1.Start();private void run()for ( int i = 0; i 101
32、; i+)label1.Text = i.ToString();private void Form1_FormClosing( object sender,FormClosingEventArgs e)if (thread1.IsAlive)thread1.Abort();运行看看,按 button1, 出错了,怎么回事呢?哈哈 看看出错原因,是在 run 函数内的 label1.Text = i.ToString(); 语句上 出的错,没错啊,语法正确啊哈哈我来解释一下,出错的原因是为了保护数据的安全所以不能跨线程调用 控件,而 label1.Text = i.ToString(); 句则是
33、在线程 thread1 上面调用主线程 的控件,肯定会出错的 怎么办呢?用委托啊(有关委托,请参考其它资料,我就不多说了) 我的理解就是,线程 thread1 不能调用主线程的 lable1 ,所以,就委托主线程 来改变 lable1 的值。首先看一个例子: (从例 3改写)( 并不创建线程,仅有主线程 ) 创建一个函数,用来设置 lable1 的值 ;private void set_lableText( string s) label1.Text = s;当需要改变 lable1 的值时,就调用它,并传递要改变的值。 整理代码如下: ( 例 6)using System;using Sys
34、tem.Windows.Forms; namespaceThreadTestpublic partial classForm1 : Formpublic Form1()InitializeComponent();private void Form1_Load( object sender, EventArgs e) label1.Text = 0;private void button1_Click( object sender, EventArgs e) run(); / 调用 run 函数private void run()for (int i=0;i101;i+)set_lableTex
35、t( i.ToString() );private void set_lableText( string s)label1.Text = s; 实现的功能与例 3 是一样的,只是,增加了一个函数。现在再来看看委托, 我们就需要委托主线程调用函数 set_lableText(string s);来改变 lable1 的值。首先声明一个委托: delegate void set_Text ( string s);创建一个全局委托变量: ( 应该是变量吧 )set_Text Set_Text; / 请注意大小写, set_Text 是委托类型, Set_Text 是创 建的委托 (当然, 这里的命名是随意的 )类似于创建线程,需要进行实例化:Set_Text = new s
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 二零二五年度个人质押担保户外运动装备质押借款合同3篇
- 2024年版权交易合同范本
- 2024年货车运输时效性与质量合同
- 2025年度办公室设备节能评估与采购合同3篇
- 二零二五年度公房转售合同模板及细则3篇
- 2024版居间人之间的合作协议
- 2024汽车展览会参展商服务与责任协议版B版
- 2025年度收养协议书标准文本汇编与解读3篇
- 2024年艺术品买卖合同:明确艺术品买卖双方在交易过程中的权益
- 2024版房产交易会参展合同协议书范本
- 活动房结构计算书
- 医疗器械经营质量管理体系文件(全套)
- 富氢水项目经济效益及投资价值分析(模板参考)
- 小流域水土保持综合治理工程初步设计
- 增强热塑性塑料复合管在我国的发展现状
- 机械设计外文文献翻译、中英文翻译、外文翻译
- 美标渐开线花键计算程序2014.8
- 英格索兰空压机操作规程
- 风动送样手册
- 绩效考核评分标准
- 电力建设施工技术管理
评论
0/150
提交评论