多线程编程

    一句话,多线程就是在做一件事的同时也可以做其他事情,就像人可以边走边说话一样。
     讨论多线程之前先了解进程和线程的概念。
进程

     进程是操作系统结构的基础;是一个正在执行的程序;计算机中正在运行的程序实例;可以分配给处理器并由处理器执行的一个实体;由单一顺序的执行显示,一个当前状态和一组相关的系统资源所描述的活动单元。它是60年代初首先由麻省理工学院的MULTICS系统和IBM公司的CTSS/360系统引入的。
  进程是一个具有独立功能的程序关于某个数据集合的一次运行活动。它可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体。它不只是程序的代码,还包括当前的活动,通过程序计数器的值和处理寄存器的内容来表示。 

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

举一个简单的例子来理解进程和线程,人的动作这就是一个进程,而人说话,摆手,眨眼等就是一系列的线程。

进程与程序的关系

  •   程序是指令的有序集合,其本身没有任何运行的含义,是一个静态的概念。而进程是程序在处理机上的一次执行过程,它是一个动态的概念。
  •   程序可以作为一种软件资料长期存在,而进程是有一定生命期的。程序是永久的,进程是暂时的。
  •   进程更能真实地描述并发,而程序不能。
  •   进程是由进程控制块、程序段、数据段三部分组成。
  •   进程具有创建其他进程的功能,而程序没有。
  •   同一程序同时运行于若干个数据集合上,它将属于若干个不同的进程。也就是说同一程序可以对应多个进程。
  •   在传统的操作系统中,程序并不能独立运行,作为资源分配和独立运行的基本单元都是进程。

进程与线程的关系

      通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源。在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位。由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度。因而近年来推出的通用操作系统都引入了线程,以便进一步提高系统的并发性,并把它视为现代操作系统的一个重要指标。


线程与进程的区别

  •   地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
  •   通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
  •   调度和切换:线程上下文切换比进程上下文切换要快得多。
  •   在多线程OS中,进程不是一个可执行的实体。


C#中的多线程开发

      在计算机编程中,一个基本的概念就是同时对多个任务加以控制。许多程序设计问题都要求程序能够停下手头的工作,改为处理其他一些问题,再返回主进程。可以通过多种途径达到这个目的。最开始的时候,那些掌握机器低级语言的程序员编写一些“中断服务例程”(比如汇编语言中的int 21h等),主进程的暂停是通过硬件级的中断实现的。尽管这是一种有用的方法,但编出的程序很难移植,由此造成了另一类的代价高昂问题。中断对那些实时性很强的任务来说是很有必要的。但对于其他许多问题,只要求将问题划分进入独立运行的程序片断中,使整个程序能更迅速地响应用户的请求。

      在.net中编写的程序将被自动分配一个线程。.net的运行时环境由Main()方法来启动应用程序,而且.net的编程语言有自动的垃圾收集功能,这个垃圾收集功能发生在另外一个线程里,所有这些当然是在后台发生的,我们不去理会这些动作。但是大多数情况下我们需要设置自己的线程来调用工作,在.net基础类库中的System.Threading命名空间中,就存在线程编程的类。


线程操作
System.Threading.Thread类是创建并操作线程,设置其优先级并获得其状态最为常用的类。

常用构造函数

构造函数 说明
Thread(ParameterizedThreadStart) 初始化 Thread 类的新实例,指定允许对象在线程启动时传递给线程的委托。
Thread(ThreadStart) 初始化 Thread 类的新实例。





常用属性

属性 说明
CurrentThread 获取当前正在运行的线程。
IsAlive 获取一个值,该值指示当前线程的执行状态。
IsBackground 获取或设置一个值,该值指示某个线程是否为后台线程。
Name 获取或设置线程的名称。
Priority 获取或设置一个值,该值指示线程的调度优先级。
ThreadState 获取一个值,该值包含当前线程的状态。









常用方法

方法 说明
Abort

 在调用此方法的线程上引发 ThreadAbortException,以开始终止此线程的过程。调用此方法通常会终止线程。

GetType

获取当前实例的 Type。
Resume 继续已挂起的线程。
Sleep 将当前线程挂起指定的时间或时间段。
Start 导致操作系统将当前实例的状态更改为 ThreadState.Running。
Suspend 挂起线程,或者如果线程已挂起,则不起作用。











  • 创建线程
    • 当然这就是实例化一个Thread类的对象,它的构造函数有一个参数ThreadStart类型的参数,这是一个委派用于传递线程的入口方法,而创建ThreadStart对象需要以一个静态方法或者是实例方法为参数。
  • 启动线程
    • 启动线程只需要用Start()方法就行。
  • 休眠线程
    • 休眠线程就是让当前线程进入一定的休眠期,时间一到线程将继续执行,当然是调用Sleep方法,它有两个参数,毫秒ms,或者是TimeSpan(时间段)。
  • 挂起线程
    • 线程的挂起就是暂停线程,如果不在启动线程,它将永远暂停,但前提是只有当前线程是运行的才可以,即判断ThreadState,之后调用Suspend方法。
  • 继续线程
    • 已经挂起的线程使用Thread的Resume方法继续进行,于是一般会先判断状态是不是挂起的。
  • 终止线程
    • 首先确定线程是不是IsAlive,即线程是不是活动着的,如果是活动着的,就调用Abort方法来终止线程。


 示例1:以winform窗体程序为例,在移动窗体的同时不能有其他代码执行,因为窗体是由UI线程创建,在执行UI中的代码时如果要进行其他代码段,那么用多线程解决。

 代码:

  1 using System;
2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.Data;
5 using System.Drawing;
6 using System.Linq;
7 using System.Text;
8 using System.Windows.Forms;
9 using System.Threading;
10
11 namespace 多线程实例
12 {
13 public partial class 多线程实例 : Form
14 {
15 public 多线程实例()
16 {
17 InitializeComponent();
18 TextBox.CheckForIllegalCrossThreadCalls = false;//由于textbox控件是由UI线程创建的,所以其他线程想要访问则系统会进行捕获对错误线程的调用,在这里关闭自动捕获这种功能。
19 }
20
21 //单线程缺点,即不能移动窗体
22 private void btnSingleThread_Click(object sender, EventArgs e)
23 {
24 this.CountTime();
25 }
26
27 //一个示例方法
28 private void ChangeValue()
29 {
30 for (int i = 0; i < 1000; i++)
31 {
32 int a = int.Parse(this.txtValue.Text.Trim());
33 Console.WriteLine(Thread.CurrentThread .Name +",a="+a);
34 a++;
35 this.txtValue.Text = a.ToString();
36 }
37 MessageBox.Show("循环完毕~~~~:)");
38
39 }
40
41 //多线程方法重入问题,会发现结果不是预期的1000
42 private void btnProblem_Click(object sender, EventArgs e)
43 {
44 ThreadStart ts=new ThreadStart (this.ChangeValue);
45 Thread myThread = new Thread(ts);
46 myThread.Name = "t1";
47 myThread.IsBackground = true;
48 myThread.Start();
49
50 ThreadStart ts2 = new ThreadStart(this.ChangeValue);
51 Thread myThread2 = new Thread(ts2);
52 myThread2.Name = "t2";
53 myThread2.IsBackground = true;
54 myThread2.Start();
55 }
56 //多线程解决
57 private void btnMulThread_Click(object sender, EventArgs e)
58 {
59 ThreadStart ts = new ThreadStart(this.CountTime );
60 Thread myThread = new Thread(ts);
61 myThread.IsBackground = true;
62 myThread.Start();
63 }
64 //一个不带参数的方法
65 private void CountTime()
66 {
67 DateTime beginTime = DateTime.Now;
68
69 for (int i = 0; i < 999999999; i++)
70 { }
71
72 TimeSpan ts =beginTime.Subtract(DateTime.Now );
73 double val = Convert.ToDouble (ts.TotalMilliseconds );
74 MessageBox.Show("循环完毕~~~~:),用时"+ Math.Abs(val)+"毫秒");
75
76 }
77 //带参数的线程
78 private void btnMulThreadWithParams_Click(object sender, EventArgs e)
79 {
80 ParameterizedThreadStart pts = new ParameterizedThreadStart(this.ShowName );
81 Thread myThread = new Thread(pts);
82 myThread.IsBackground = true;
83 myThread.Start(this.txtValue.Text.Trim ());
84 }
85
86 //带参数的方法,如果要用在线程中实现,则必须是object类型
87 private void ShowName(object name)
88 {
89 MessageBox.Show("name="+name.ToString());
90 }
91
92 //带多参数的多线程
93 private void btnMulThreadWithMulParams_Click(object sender, EventArgs e)
94 {
95 ParameterizedThreadStart pts = new ParameterizedThreadStart(this.ShowName2);
96 Thread myThread = new Thread(pts);
97 myThread.IsBackground = true;
98 myThread.Start(new List<string>() { "123","456"});
99 }
100
101 //带参数的方法,如果要用在线程中实现,则必须是object类型
102 private void ShowName2(object name)
103 {
104 List <string> list=name as List<string>;
105 if(list !=null)
106 {
107 foreach (string s in list )
108 {
109 MessageBox.Show("name=" +s);
110 }
111 }
112 }
113 }
114 }

 

设计界面

示例2:简单的线程操作

代码:

 1 using System;
2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.Data;
5 using System.Drawing;
6 using System.Linq;
7 using System.Text;
8 using System.Windows.Forms;
9 using System.Threading;
10
11 namespace 多线程实例
12 {
13 public partial class 线程操作方法 : Form
14 {
15 public 线程操作方法()
16 {
17 InitializeComponent();
18 Label.CheckForIllegalCrossThreadCalls = false;//关闭线程的检测
19 }
20
21 Thread myThread = null;//声明一个线程
22
23 /// <summary>
24 /// 线程调用的方法,显示当前时间
25 /// </summary>
26 private void ShowTime()
27 {
28 while (true)
29 {
30 this.lblShow.Text = DateTime.Now.ToString();
31 }
32 }
33
34 //页面初始化,显示当前时间
35 private void 线程操作方法_Load(object sender, EventArgs e)
36 {
37 this.lblShow.Text += DateTime.Now.ToString();
38 }
39
40 //创建线程
41 private void ThreadCreate_Click(object sender, EventArgs e)
42 {
43 ThreadStart ts = new ThreadStart(ShowTime);
44 myThread = new Thread(ts);//初始化线程
45 MessageBox.Show("创建线程成功");
46 //myThread.IsBackground = true;//设置为后台线程
47 }
48 //启动线程
49 private void ThreadStart_Click(object sender, EventArgs e)
50 {
51 myThread.Start();
52 }
53 //休眠线程
54 private void ThreadSleep_Click(object sender, EventArgs e)
55 {
56 Thread.Sleep(new TimeSpan(0,0,0,3,0));//休眠3秒
57 }
58 //挂起线程
59 private void ThreadSuspend_Click(object sender, EventArgs e)
60 {
61 if (myThread.ThreadState==ThreadState.Running)//判断线程是否是运行的
62 {
63 myThread.Suspend();//挂起
64 }
65 }
66 //继续线程
67 private void ThreadResume_Click(object sender, EventArgs e)
68 {
69 if (myThread.ThreadState==ThreadState.Suspended)//判断线程是否是挂起的
70 {
71 myThread.Resume();//继续线程
72 }
73 }
74 //终止线程
75 private void ThreadAbort_Click(object sender, EventArgs e)
76 {
77 if (myThread.IsAlive)//判断线程是否是活动的
78 {
79 myThread.Abort();//终止线程
80 }
81 }
82 }
83 }

界面:

线程同步

      在包含多个线程的应用程序中,线程间有时会共享存储空间,当两个或多个线程同时访问统一资源时,必然会冲突。如果两个线程分别执行读、写操作,那么结果将不是预期的结果。因此我们要保证线程的一个访问次序,即按照一定的规则使某一个线程先访问资源,另一个线程后访问。
      在C#.Net中,提供了多种线程同步的方法,如加锁(Lock)、监视器(Monitor)、互斥体(Mutex)。

 

  • 加锁(Lock)
  • 实现多线程同步的最简单的方法就是加锁,使用lock语句就行,它可以把一段代码定义为互斥段,在一个时刻内只允许一个线程进入执行,其他线程进行等待。
  • 格式:lock (expression) statement_block
  • expression代表要加锁的对象,必须是一引用类型。一般的,如果要保护一个类的实例成员,可以使用this,如果要保护一个静态成员,或者要保护的内容位于一个静态方法中,可以使用类名,格式为:lock(typeof(类名))。
  • statement_block代表共享资源。


  • 监视器(Monitor)
  • Monitor的功能和lock有些类似,但是比lock更加强大与灵活。
  • 位于System.Threading命名空间中的Monitor类是一个静态类,当然它所有的方法都是静态方法。它通过Enter方法向单个线程授予锁定对象的“钥匙”来控制锁定对象,该方法提供限制访问代码块(通常称为临界区,由Enter方法标记临界区的开头,Exit方法来标记临界区的结尾)的功能。

 

  • 互斥体(Mutex)
  • 互斥体是通过只向一个线程授予对共享资源的独占访问权。如果一个线程获取了互斥体,则要获取该互斥体的第二个线程将被挂起,直到第一个线程释放该互斥体。在System.Threading命名空间中的Mutex类代表了互斥体,它继承于WaitHandle类,该类代表了所有的同步对象。

示例3:这个例子来源于《C#网络编程技术教程》,是一个模拟吃苹果的例子,要求:一家有三个孩子,爸爸妈妈不断削平果往盘子里放,老大、老二、老三不断从盘子里拿苹果吃。盘子只能放5个苹果,并且爸爸妈妈不能同时往盘子里放苹果,妈妈具有优先权。三个孩子取苹果吃的时候盘子不能是空的,三人不能同时取,老三优先权最高,老大最低,老大吃的最快,取得频率最高,老二次之。

代码:

  1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading;
6
7 namespace 多线程实例
8 {
9 class EatAppleSmp
10 {
11 public EatAppleSmp()
12 {
13 Thread th_mother, th_father, th_young, th_middle, th_old;
14 Dish dish = new Dish(this, 30);
15 //建立线程
16 Productor mother = new Productor("妈妈", dish);
17 Productor father = new Productor("爸爸", dish);
18 Consumer old = new Consumer("老大",dish,1000);
19 Consumer middle = new Consumer("老二",dish,1200);
20 Consumer young = new Consumer("老三",dish,1500);
21 th_mother = new Thread(new ThreadStart(mother.run));
22 th_father = new Thread(new ThreadStart(father.run));
23 th_old = new Thread(new ThreadStart(old.run));
24 th_mother = new Thread(new ThreadStart(mother.run));
25 th_middle = new Thread(new ThreadStart(middle.run));
26 th_young = new Thread(new ThreadStart(young.run));
27 //设置优先级
28 th_mother.Priority = ThreadPriority.Highest;
29 th_father.Priority = ThreadPriority.Normal;
30 th_old.Priority = ThreadPriority.Lowest;
31 th_middle.Priority = ThreadPriority.Normal;
32 th_young.Priority = ThreadPriority.Highest;
33 th_mother.Start();
34 th_father.Start();
35 th_old.Start();
36 th_middle.Start();
37 th_young.Start();
38 }
39
40 static void Main(string[] args)
41 {
42 EatAppleSmp mainStart = new EatAppleSmp();
43 }
44 }
45 class Dish
46 {
47 int f = 5;//盘子中最多五个苹果
48 EatAppleSmp oEAP;
49 int EnableNum;//可放苹果总数
50 int n = 0;
51
52 public Dish(EatAppleSmp oEAP, int EnableNum)
53 {
54 this.oEAP = oEAP;
55 this.EnableNum = EnableNum;
56 }
57 public void put(string name)
58 {
59 lock (this)//同步控制放苹果
60 {
61 while (f == 0)//苹果已满时,线程等待
62 {
63 try
64 {
65 Console.WriteLine(name+"正在等地放入苹果");
66 Monitor.Wait(this);
67 }
68 catch (ThreadInterruptedException)
69 {
70
71 throw;
72 }
73 }
74 f = f - 1;//削完一个苹果放一个
75 n = n + 1;
76 Console.WriteLine(name+"放入一个苹果");
77 Monitor.PulseAll(this);
78 if (n > this.EnableNum)
79 {
80 Thread.CurrentThread.Abort();
81 }
82 }
83 }
84 public void get(string name)
85 {
86 lock (this)//同步控制取苹果
87 {
88 while (f == 5)
89 {
90 try
91 {
92 Console.WriteLine(name+"等待取苹果");
93 Monitor.Wait(this);
94 }
95 catch (ThreadInterruptedException)
96 {
97
98 throw;
99 }
100 }
101 f = f + 1;
102 Console.WriteLine(name+"取苹果吃...");
103 Monitor.PulseAll(this);
104 }
105 }
106 }
107
108 class Productor
109 {
110 private Dish dish;
111 private string name;
112
113 public Productor(string name, Dish dish)
114 {
115 this.dish = dish;
116 this.name = name;
117 }
118 public void run()
119 {
120 while (true)
121 {
122 dish.put(name);
123 try
124 {
125 Thread.Sleep(600);//削苹果时间
126 }
127 catch (ThreadInterruptedException )
128 {
129
130 throw;
131 }
132 }
133 }
134 }
135
136 class Consumer
137 {
138 private string name;
139 private Dish dish;
140 private int timelong;
141
142 public Consumer(string name, Dish dish, int timelong)
143 {
144 this.name = name;
145 this.dish = dish;
146 this.timelong = timelong;
147 }
148 public void run()
149 {
150 while (true)
151 {
152 dish.get(name);
153 try
154 {
155 Thread.Sleep(600);//吃苹果时间
156 }
157 catch (ThreadInterruptedException )
158 {
159
160 throw;
161 }
162 }
163 }
164
165 }
166 }

 


posted on 2012-02-28 13:43  WaitingSky  阅读(391)  评论(0编辑  收藏  举报