C#编程总结(二)多线程基础

无论您是为具有单个处理器的计算机还是为具有多个处理器的计算机进行开发,您都希望应用程序为用户提供最好的响应性能,即使应用程序当前正在完成其他工作。要使应用程序能够快速响应用户操作,同时在用户事件之间或者甚至在用户事件期间利用处理器,最强大的方式之一是使用多线程技术。

多线程:线程是程序中一个单一的顺序控制流程.在单个程序中同时运行多个线程完成不同的工作,称为多线程。如果某个线程进行一次长延迟操作, 处理器就切换到另一个线程执行。这样,多个线程的并行(并发)执行隐藏了长延迟,提高了处理器资源利用率,从而提高了整体性能。多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率

一、进程与线程

进程,是操作系统进行资源调度和分配的基本单位。是由进程控制块、程序段、数据段三部分组成。一个进程可以包含若干线程(Thread),线程可以帮助应用程序同时做几件事(比 如一个线程向磁盘写入文件,另一个则接收用户的按键操作并及时做出反应,互相不干扰),在程序被运行后中,系统首先要做的就是为该程序进程建立一个默认线程,然后程序可 以根据需要自行添加或删除相关的线程。它是可并发执行的程序。在一个数据集合上的运行过程,是系统进行资源分配和调度的一个独立单位,也是称活动、路径或任务,它有两方面性质:活动性、并发性。进程可以划分为运行、阻塞、就绪三种状态,并随一定条件而相互转化:就绪--运行,运行--阻塞,阻塞--就绪。

线程(thread),线程是CPU调度和执行的最小单位。有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。

主线程,进程创建时,默认创建一个线程,这个线程就是主线程。主线程是产生其他子线程的线程,同时,主线程必须是最后一个结束执行的线程,它完成各种关闭其他子线程的操作。尽管主线程是程序开始时自动创建的,它也可以通过Thead类对象来控制,通过调用CurrentThread方法获得当前线程的引用

多线程的优势:进程有独立的地址空间,同一进程内的线程共享进程的地址空间。启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。

二、多线程优点

1、提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。
2、使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
3、改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。。

多线程尽管优势明显,但是线程并发冲突、同步以及管理跟踪,可能给系统带来很多不确定性,这些必须引起足够重视。

废话不多说开始我们的多线程之旅。

三、多线程的应用场合:

简单总结了一下,一般有两种情况:

1)多个线程,完成同类任务,提高并发性能

2)一个任务有多个独立的步骤,多个线程并发执行各子任务,提高任务处理效率

四、案例--搬运工

在我们现实生活中,经常看到这样的场景。有一堆货物,有几个搬运工负责将货物搬运到指定地点。但是搬运工能力不同,有人一次能搬多箱,有人走路比较慢,搬运一趟的时间间隔比较长。搬运工,各自搬运,无先后,互不干扰。我们如何在程序中实现这种场景呢?

案例分析:
这个就是最简单的多线程的实际案例。每个人相当于一个线程,并发执行。当货物搬运完毕后,每个线程自动停止。这里暂时不考虑死锁情况。

案例代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading; namespace MutiThreadSample.Transport
{
/// <summary>
/// 搬运工
/// </summary>
public class Mover
{
/// <summary>
/// 总数
/// </summary>
public static int GoodsTotal { get; set; }
/// <summary>
/// 间隔时间
/// </summary>
public static int IntervalTime { get; set; }
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 单位时间搬运量
/// </summary>
public int LaborAmount { get; set; }
/// <summary>
/// 搬运
/// </summary>
public void Move()
{
while (GoodsTotal > )
{
GoodsTotal -= LaborAmount;
Console.WriteLine("搬运者:{0} 于 {1} 搬运货物 {2}",this.Name,DateTime.Now.Millisecond,this.LaborAmount);
Thread.Sleep(IntervalTime);
Console.WriteLine("搬运者:{0} Continue",this.Name);
}
}
/// <summary>
/// 搬运
/// </summary>
/// <param name="interval">时间间隔</param>
public void Move(object interval)
{
int tempInterval = ;
if (!int.TryParse(interval.ToString(), out tempInterval))
{
tempInterval = IntervalTime;
}
while (GoodsTotal > )
{
GoodsTotal -= LaborAmount;
Console.WriteLine("搬运者:{0} 于 {1} 搬运货物 {2}", this.Name, DateTime.Now.Millisecond, this.LaborAmount);
Thread.Sleep(tempInterval);
}
}
}
}

测试:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading; namespace MutiThreadSample.Transport
{
/// <summary>
/// 测试搬运
/// </summary>
public class TestMove
{
/// <summary>
/// 搬运
/// </summary>
public static void Move()
{
//测试搬运工
Mover.GoodsTotal = ;
Mover.IntervalTime = ;
Mover m1 = new Mover() { Name = "Tom", LaborAmount = };
Mover m2 = new Mover() { Name = "Jim", LaborAmount = };
Mover m3 = new Mover() { Name = "Lucy", LaborAmount = }; List<Mover> movers = new List<Mover>();
movers.Add(m1);
//movers.Add(m2);
//movers.Add(m3); if (movers != null && movers.Count > )
{
foreach (Mover m in movers)
{
Thread thread = new Thread(new ThreadStart(m.Move));
thread.Start();
}
} //Main Thread continue
// validate Thread.Sleep()
//int i =0;
//int j = 0;
//while (i < 10)
//{
// while(j<10000000)
// {
// j++;
// }
// Console.WriteLine("CurrentThread:{0}", Thread.CurrentThread.Name);
// i++;
//} } /// <summary>
/// 搬运
/// </summary>
public static void MoveWithParamThread()
{
//测试搬运工
Mover.GoodsTotal = ;
Mover.IntervalTime = ;
Mover m1 = new Mover() { Name = "Tom", LaborAmount = };
Mover m2 = new Mover() { Name = "Jim", LaborAmount = };
Mover m3 = new Mover() { Name = "Lucy", LaborAmount = }; List<Mover> movers = new List<Mover>();
movers.Add(m1);
movers.Add(m2);
movers.Add(m3); if (movers != null && movers.Count > )
{
foreach (Mover m in movers)
{
Thread thread = new Thread(new ParameterizedThreadStart(m.Move));
thread.Start();
}
}
}
}
}

通过案例我们也接触了Thread,下面我们将详细介绍Thread的功能。

五、Thread

创建并控制线程,设置其优先级并获取其状态。

常用方法:

Start()

导致操作系统将当前实例的状态更改为 ThreadState.Running。

一旦线程处于 ThreadState.Running 状态,操作系统就可以安排其执行。 线程从方法的第一行(由提供给线程构造函数的 ThreadStart 或 ParameterizedThreadStart 委托表示)开始执行。线程一旦终止,它就无法通过再次调用 Start 来重新启动。

Thread.Sleep()

调用 Thread.Sleep 方法会导致当前线程立即阻止,阻止时间的长度等于传递给 Thread.Sleep 的毫秒数,这样,就会将其时间片中剩余的部分让与另一个线程。 一个线程不能针对另一个线程调用 Thread.Sleep。

Interrupt()
中断处于 WaitSleepJoin 线程状态的线程。

Suspend和Resume(已过时)
挂起和继续
在 .NET Framework 2.0 版中,Thread.Suspend 和 Thread.Resume 方法已标记为过时,并将从未来版本中移除。

Abort()
方法用于永久地停止托管线程。一旦线程被中止,它将无法重新启动。

Join()
阻塞调用线程,直到某个线程终止时为止。

ThreadPriority(优先级)
指定 Thread 的调度优先级。
ThreadPriority 定义一组线程优先级的所有可能值。线程优先级指定一个线程相对于另一个线程的相对优先级。
每个线程都有一个分配的优先级。在运行库内创建的线程最初被分配 Normal 优先级,而在运行库外创建的线程在进入运行库时将保留其先前的优先级。可以通过访问线程的 Priority 属性来获取和设置其优先级。
根据线程的优先级调度线程的执行。用于确定线程执行顺序的调度算法随操作系统的不同而不同。操作系统也可以在用户界面的焦点在前台和后台之间移动时动态地调整线程的优先级。
一个线程的优先级不影响该线程的状态;该线程的状态在操作系统可以调度该线程之前必须为 Running。

六、创建线程方式

通过搬运工案例我们能够了解线程的工作原理,也明白了线程的创建方式。

其实在C#中创建线程有几种方式,这里给大家举几个常用例子,如下:

using System;
using System.Threading; namespace MutiThreadSample
{
/// <summary>
/// 创建线程的方式
/// </summary>
class CreateThread
{
/// <summary>
/// 不带参数的委托
/// </summary>
public void CreateThreadWithThreadStart()
{
Thread thread = new Thread(new ThreadStart(ThreadCallBack));
thread.Start();
}
/// <summary>
/// 带参数的委托
/// </summary>
public void CreateThreadWithParamThreadStart()
{
Thread thread = new Thread(new ParameterizedThreadStart(ThreadCallBackWithParam));
Object param = null;
thread.Start(param);
}
/// <summary>
/// 匿名函数
/// </summary>
public void CreateThreadWithAnonymousFunction()
{
Thread thread = new Thread(delegate()
{
Console.WriteLine("进入子线程1");
for (int i = ; i < ; ++i)
{
Thread.Sleep();
Console.WriteLine("\t+++++++子线程1+++++++++");
}
Console.WriteLine("退出子线程1");
});
thread.Start();
}
/// <summary>
/// 直接赋值委托
/// </summary>
public void CreateThreadWithCallBack()
{
Thread _hThread = new Thread(ThreadCallBack);
_hThread.Start(); }
/// <summary>
/// 无参数的方法调用
/// </summary>
public void ThreadCallBack()
{
// Do Something
}
/// <summary>
/// 带参数的方法
/// </summary>
/// <param name="obj"></param>
public void ThreadCallBackWithParam(object obj)
{
// Do Something
}
}
}

时钟线程

使用 TimerCallback 委托指定希望 Timer 执行的方法。 计时器委托在构造计时器时指定,并且不能更改。 此方法不在创建计时器的线程上执行,而是在系统提供的 ThreadPool 线程上执行。创建计时器时,可以指定在第一次执行方法之前等待的时间量(截止时间)以及此后的执行期间等待的时间量(时间周期)。 可以使用 Change 方法更改这些值或禁用计时器。

using System;
using System.Threading; class TimerExample
{
static void Main()
{
// Create an event to signal the timeout count threshold in the
// timer callback.
AutoResetEvent autoEvent = new AutoResetEvent(false); StatusChecker statusChecker = new StatusChecker(); // Create an inferred delegate that invokes methods for the timer.
TimerCallback tcb = statusChecker.CheckStatus; // Create a timer that signals the delegate to invoke
// CheckStatus after one second, and every 1/4 second
// thereafter.
Console.WriteLine("{0} Creating timer.\n",
DateTime.Now.ToString("h:mm:ss.fff"));
Timer stateTimer = new Timer(tcb, autoEvent, , ); // When autoEvent signals, change the period to every
// 1/2 second.
autoEvent.WaitOne(, false);
stateTimer.Change(, );
Console.WriteLine("\nChanging period.\n"); // When autoEvent signals the second time, dispose of
// the timer.
autoEvent.WaitOne(, false);
stateTimer.Dispose();
Console.WriteLine("\nDestroying timer.");
}
} class StatusChecker
{
private int invokeCount;
private int maxCount; public StatusChecker(int count)
{
invokeCount = ;
maxCount = count;
} // This method is called by the timer delegate.
public void CheckStatus(Object stateInfo)
{
AutoResetEvent autoEvent = (AutoResetEvent)stateInfo;
Console.WriteLine("{0} Checking status {1,2}.",
DateTime.Now.ToString("h:mm:ss.fff"),
(++invokeCount).ToString()); if(invokeCount == maxCount)
{
// Reset the counter and signal Main.
invokeCount = ;
autoEvent.Set();
}
}
}

七、前台线程和后台线程

.Net的公用语言运行时(Common Language Runtime,CLR)能区分两种不同类型的线程:前台线程和后台线程。这两者的区别就是:应用程序必须运行完所有的前台线程才可以退出;而对于后台线程,应用程序则可以不考虑其是否已经运行完毕而直接退出,所有的后台线程在应用程序退出时都会自动结束。

一个线程是前台线程还是后台线程可由它的IsBackground属性来决定。这个属性是可读又可写的。它的默认值为false,即意味着一个线程默认为前台线程。

我们可以将它的IsBackground属性设置为true,从而使之成为一个后台线程。下面的例子是一个控制台程序,程序一开始便启动了10个线程,每个线程运行5秒钟时间。由于线程的IsBackground属性默认为false,即它们都是前台线程,所以尽管程序的主线程很快就运行结束了,但程序要到所有已启动的线程都运行完毕才会结束。示例代码如下例子中的Test()所示

using System;
using System.Threading; namespace MutiThreadSample.ThreadType
{
class ThreadTypeTest
{
/// <summary>
/// 测试前台线程
/// </summary>
public static void Test()
{
for (int i = ; i < ; i++)
{
Thread thread = new Thread(new ThreadStart(ThreadFunc));
thread.Start();
}
}
/// <summary>
/// 测试后台线程
/// </summary>
public static void TestBackgroundThread()
{
for (int i = ; i < ; i++)
{
Thread thread = new Thread(new ThreadStart(ThreadFunc));
thread.IsBackground = true;
thread.Start();
}
}
public static void ThreadFunc()
{
Thread.Sleep();
DateTime start = DateTime.Now;
while ((DateTime.Now - start).Seconds < );//可以停顿的时间长一点,效果更加明显
}
}
}

接下来我们对上面的代码进行略微修改,将每个线程的IsBackground属性都设置为true,则每个线程都是后台线程了。那么只要程序的主线程结束了,整个程序也就结束了。示例代码如代码中的TestBackgroundThread()。

这个例子直接创建一个控制台程序即可检验。

前台和后台线程的使用原则

既然前台线程和后台线程有这种差别,那么我们怎么知道该如何设置一个线程的IsBackground属性呢?下面是一些基本的原则:对于一些在后台运行的线程,当程序结束时这些线程没有必要继续运行了,那么这些线程就应该设置为后台线程。比如一个程序启动了一个进行大量运算的线程,可是只要程序一旦结束,那个线程就失去了继续存在的意义,那么那个线程就该是作为后台线程的。而对于一些服务于用户界面的线程往往是要设置为前台线程的,因为即使程序的主线程结束了,其他的用户界面的线程很可能要继续存在来显示相关的信息,所以不能立即终止它们。这里我只是给出了一些原则,具体到实际的运用往往需要编程者的进一步仔细斟酌。

八、总结

这一章主要介绍多线程技术的基本知识。涉及多线程的具体应用,包括预防死锁、线程同步、线程池等,在今后的文章会涉及到。

C#编程总结(二)多线程基础的更多相关文章

  1. 《java编程思想》--多线程基础--Runnable

    一.简单说下Runnable是什么 1.它是一个接口 2.只提供了run方法 3.这个接口提供了一个协议:实现这个接口的类是active的(不必成为Thread的子类) 4.run方法没有返回值 /* ...

  2. Day04:异常处理(二) / 多线程基础

    多线程 线程是什么? 一个线程是线程一个顺序执行流. 同类的多个线程共享一块内存空间和一组系统资源,线程本身有一个供程序执行时的栈堆.线程在切换时负荷小,因此,线程也被称为轻负荷进程.一个进程中可以包 ...

  3. 浅谈PHP面向对象编程(二、基础知识)

    和一些面向对象的语言有所不同,PHP并不是一种纯面向对象的语言,包PIP它支持面向对象的程序设计,并可以用于开发大型的商业程序.因此学好面向对象输程对PHP程序员来说也是至关重要的.本章并针对面向对象 ...

  4. Scala 基础(十):Scala 函数式编程(二)基础(二)过程、惰性函数、异常

    1 过程 将函数的返回类型为Unit的函数称之为过程(procedure),如果明确函数没有返回值,那么等号可以省略 注意事项和细节说明 1)注意区分: 如果函数声明时没有返回值类型,但是有 = 号, ...

  5. 量子计算机编程(二)——QPU基础函数

    第二部分主要是QPU的基础功能,第一部分就像是我们有了哪些基本的语句,第二部分就是我们能写一些简单基础的函数,一些小模块,第三部分就是他的应用了. 先来看一下一个简单量子应用的结构: 第一步,将量子态 ...

  6. Java 多线程基础(十二)生产者与消费者

    Java 多线程基础(十二)生产者与消费者 一.生产者与消费者模型 生产者与消费者问题是个非常典型的多线程问题,涉及到的对象包括“生产者”.“消费者”.“仓库”和“产品”.他们之间的关系如下: ①.生 ...

  7. Java多线程编程核心技术(二)对象及变量的并发访问

    本文主要介绍Java多线程中的同步,也就是如何在Java语言中写出线程安全的程序,如何在Java语言中解决非线程安全的相关问题.阅读本文应该着重掌握如下技术点: synchronized对象监视器为O ...

  8. Java并发编程(二)为什么需要多线程

    如果不考虑多线程的话,那么在程序只有一条执行路径,代码串行执行:顺序执行.选择或者循环.单线程就像你用你惯常的手去写字,多线程编程就要求你左手画圆,右手画方.一不留神就会手忙脚乱,圆不是圆,方也不像方 ...

  9. Java多线程干货系列—(一)Java多线程基础

    前言 多线程并发编程是Java编程中重要的一块内容,也是面试重点覆盖区域,所以学好多线程并发编程对我们来说极其重要,下面跟我一起开启本次的学习之旅吧. 正文 线程与进程 1 线程:进程中负责程序执行的 ...

随机推荐

  1. 【转】优化Web程序的最佳实践

    自动排版有点乱,看着蛋疼,建议下载中文PDF版阅读或阅读英文原文. Yahoo!的Exceptional Performance团队为改善Web性能带来最佳实践.他们为此进行了 一系列的实验.开发了各 ...

  2. ios 截屏

    把当前屏幕作为获取成为图片 - (UIImage *)rn_screenshot {    UIGraphicsBeginImageContext(self.bounds.size);    [sel ...

  3. redis中使用java脚本实现分布式锁

    转载于:http://www.itxuexiwang.com/a/shujukujishu/redis/2016/0216/115.html?1455860390 edis被大量用在分布式的环境中,自 ...

  4. Atitit。木马病毒原理机密与概论以及防御

    Atitit.木马病毒原理机密与概论以及防御 1. 定时截屏木马1 1.1. QQ聊天与微信聊天木马1 2. 文档木马1 3. 病毒木马的触发方式2 4. 远程木马2 5. 漏洞木马2 6. 病毒木马 ...

  5. linux epoll模型使用注意点

    1.默认使用的水平触发方式会多次触发回调函数,但是事实上这时并不需要回调,会浪费系统性能,就是在注册

  6. KnockoutJS 3.X API 第四章 数据绑定(5) 控制流component绑定

    本节目录: 一个例子 API 备注1:仅模板式的component 备注2:component虚拟绑定 备注3:传递标记到component绑定 内存管理 一个例子 First instance, w ...

  7. Java-集合-第三题 有如下Student 对象, private String name; private int age; private int score; private String classNum; 其中,classNum 表示学生的班号,例如“class05”。 有如下List List list = new ArrayList(); l

    第三题 有如下Student 对象, private String name; private int age; private int score; private String classNum; ...

  8. CSS3伸缩布局Flex学习笔记

    如果需要使用伸缩布局首先得把display:flex;对于兼容还得加前缀display:-webkit-display:flex;等其他浏览器前缀,但我本机Chrome测试已经不需要加前缀了,其实这些 ...

  9. 浅谈Winform事件的实现以及模拟其事件的实现(附实现源码)

    当我们初学Winform的时候被其神奇的事件功能所吸引,当点击一个按钮时,便会跳到我们所写的点击方法当中去.然而这并不符合我们对方法的理解,究竟.net在后面帮助我们实现了什么.我们怎样模拟其事件的实 ...

  10. Android控件之WebView

    如何在Android应用中打开Web网站呢?谷歌为我们提供了解决方案,现在就让我们一起看一下WebView控件吧. 为了方便总结,就以实现下面这个效果为主线,进行总结: 首先我们先看一下它的布局文件吧 ...