一句话,多线程就是在做一件事的同时也可以做其他事情,就像人可以边走边说话一样。
讨论多线程之前先了解进程和线程的概念。
进程
进程是操作系统结构的基础;是一个正在执行的程序;计算机中正在运行的程序实例;可以分配给处理器并由处理器执行的一个实体;由单一顺序的执行显示,一个当前状态和一组相关的系统资源所描述的活动单元。它是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 }