首页 > 学院 > 开发设计 > 正文

C#多线程(一)

2019-11-17 03:15:53
字体:
来源:转载
供稿:网友

C#多线程(一)

2014-03-27 16:49 by OshynSong, ... 阅读, ... 评论, 收藏, 编辑

一、定义与理解

1、定义

线程是操作系统分配CPU时间片的基本单位,每个运行的引用程序为一个进程,这个进程可以包含一个或多个线程。

线程是进程中的执行流程,每个线程可以得到一小段程序的执行时间,在单核处理器中,由于切换线程速度很快因此感觉像是线程同时允许,其实任意时刻都只有一个线程运行,但是在多核处理器中,可以实现混合时间片和真实的并发执行。但是由于操作系统自己的服务或者其他应用程序执行,也不能保证一个进程中的多个线程同时运行。

线程被一个CLR委托给操作系统的进程协调函数管理,确保所有线程都可以被分配适当的执行时间,同时保证在等待或阻止的线程不占用执行时间。

2、理解

线程与进程的关键区别是:进程是彼此隔离的,进程是操作系统分配资源的基本单位,而同一个进程中的多个线程是共享该进程内存堆区(Heap)的数据的,可以进行直接的数据共享。但是对于同一进程内的不同线程维护各自的内存栈(Stack),因此各线程的局部变量是隔离的。通过下面的例子可以看出。

[csharp]view plaincopyPRint?在CODE上查看代码片staticvoidMain(string[]args)
  • {
  • Threadt=newThread(Write);
  • t.Start();
  • Write();
  • Console.ReadKey();
  • }
  • staticvoidWrite()
  • {
  • for(inti=0;i<5;i++)
  • Console.Write("@");
  • }

    结果输出的是10个“@”,在两个线程中都有局部变量i,是彼此隔离的。但是对于共享的引用变量和静态数据,多个线程是会产生不可预知的结果的,这里共享的数据也就是“临界数据”,从而引发了线程安全的概念。

    [csharp]view plaincopyprint?在CODE上查看代码片staticbooldone;
  • staticvoidMain(string[]args)
  • {
  • Threadt=newThread(Write);
  • t.Start();
  • Write();
  • Console.ReadKey();
  • }
  • staticvoidWrite()
  • {
  • if(!done)
  • {
  • done=true;
  • Console.Write("@");
  • }
  • }

    这里输出的只有一个字符,但是很可能在极少数情况下会出现输出两个字符的情况,而且这是不可预知的。但是,对于共享的引用就不会出现这种情况。

    二、线程使用情形

    • 客户端应用程序保持对用户的响应:由于某些应用程序的特定需求,多线程程序一般用来执行需要非常耗时的操作,此时使用主线程创建工作线程在后台执行耗时的任务,而主线程保持运行,例如保持与用户的交互(更新进度条、显示提示文字等),这样可以防止由于程序耗时而被操作系统提示“无响应”而被用户强制关闭进程。
    • 及时处理请求:对于Web应用程序,主线程相应客户端用户的请求,返回数据的同时,工作线程从数据库选出最新数据。这样可以对某些实时性要求高的应用非常有效,同时可以查询工作量被单独线程分开执行,特别是在多核处理器上,可以提高程序的性能。同时对于服务器需要处理多种类型的请求的时候,如asp.net、WCF、Remoting等,从而可以实现并发响应。
    • 防止一个线程长时间没有响应而阻塞CPU来提高效率:例如WebService服务,对于没有用户交互界面的访问,在等待提供webservice服务(比较耗时)的电脑的响应的同时可以执行其他工作,以提高效率。

    问题:

    多线程的问题是使程序中的多个线程的交互变得过于复杂,会带来较长的开发时间和间歇性或非重复性的bug。同时线程数目不能太多,否则频繁的分配和切换线程会带来资源和CPU的开销,一般有一个到两个工作线程就足够。

    三、C#中的线程

    C#中主要使用Thread类进行线程操作,位于System.Threading命名空间下,提供了一系列进行多线程编程的类和接口,有线程同步和数据访问的Mutex、Monitor、Interlocked和AutoResetEvent类,以及ThreadPool类和Timer类等。

    首先使用new Thread()创建出新的线程,然后调用Start方法使得线程进入就绪状态,得到系统资源后就执行,在执行过程中可能有等待、休眠、死亡和阻塞四种状态。正常执行结束时间片后返回到就绪状态。如果调用Suspend方法会进入等待状态,调用Sleep或者遇到进程同步使用的锁机制而休眠等待。具体过程如下图所示:

    Thread类主要用来创建并控制线程,设置线程的状态、优先级等。创建线程的时候使用ThreadStart委托或者ParameterizedThreadStart委托来执行线程所关联的部分代码(也就是工作线程的运行代码)。

    Thread类属性
    属性说明
    CurrentThread获取当前正在运行的线程
    IsAlive获取当前线程的执行状态
    Name获取或设置线程的名称
    Priority获取或设置线程的优先级
    ThreadState获取包含当前线程状态的值
    Thread类常用方法
    方法说明
    Abort调用此方法的线程引发ThreadAbortException终止线程
    Join阻止调用线程,知道某个线程终止时为止
    Resume继续已挂起的线程
    Sleep将线程阻止指定的毫秒数
    Start将线程安排被进行执行
    Suspent挂起线程,如果已经挂起则不起作用

    四、创建与运行设置

    1、创建

    使用Thread类的构造函数创建线程的时候,需要传递一个新线程开始执行的代码块,提供了使用无参数的TheadStart委托和带有一个参数的ParameterizedTheadStart委托。他们的定义如下:

    [csharp]view plaincopyprint?在CODE上查看代码片publicdelegatevoidThreadStart();
  • publicdelegatevoidParameterizedThreadStart(objectobj);
  • 任何时候C#使用上述两个委托中的一个自动进行线程的创建。

    [csharp]view%20plaincopyprint?staticvoidMain()
  • {
  • Threadt=newThread(newTheadStart(Go));
  • t.Start();
  • Go();
  • }
  • staticvoidGo()
  • {
  • Console.Write("hello!");
  • }
  • 上述方式不传递参数,可以使用new%20Thead(Go)的方式直接创建,此时C#会在编译时自动匹配使用的是ThreadStart委托创建的。下面可以进行传递参数创建线程。

    [csharp]view%20plaincopyprint?staticvoidMain()
  • {
  • Threadt=newThread(Go);
  • t.Start("hello");
  • Go();
  • }
  • staticvoidGo(objectmsg)
  • {
  • stringmessage=(string)msg;
  • Console.Write(message);
  • }

    此时实际在编译时使用的new%20Thread(new%20ParameterizedThreadStart(Go("hello")))创建的,上述使用Start方法传递的参数会默认采用这种方式构建。

    第二种方法是使用Lambda表达式:

    [csharp]view%20plaincopyprint?newThread(()=>Go("hello"));

    第三种方法是使用匿名方法:

    [csharp]view%20plaincopyprint?newThread(()=>{
  • Console.Write("helloworld!");
  • ......
  • }).Start();
  • 注意问题:使用Lambda表达式的时候会存在变量捕获的问题,如果捕获的变量是共享的,会出现线程不安全的问题。看下面的例子:

    [csharp]view%20plaincopyprint?staticvoidMain(string[]args)
  • {
  • for(inti=0;i<10;i++)
  • newThread(()=>Write(i)).Start();
  • Console.ReadKey();
  • }
  • staticvoidWrite(objectobj)
  • {
  • stringmsg=Convert.ToString(obj);
  • Console.Write(msg);
  • }
  • 上述由于使用Lambda表达式传递参数,在for循环的作用域内,新建的十个线程共享了局部变量i,传递进入i参数可能被多个线程已经修改,因此每次输出结果都是不确定的,两次结果如下:

    上述问题,可以使用在循环体内使用一个tmp变量保存每次的变量i值,这样输出的就是0到9这十个数。因为使用tmp变量之后的代码可以用下面的来理解:

    发表评论 共有条评论
    用户名: 密码:
    验证码: 匿名发表