进程和线程介绍
进程是一个独立的运行环境,可以被看作一个程序或者一个应用。线程是在进程中执行的一个任务单元。
区别:
(1)进程占有一定的内存地址空间,所以进程间存在内存隔离,数据是分开的,数据共享复杂但是同步简单,各个进程之间互不干扰;而线程共享所属进程占有的内存地址空间和资源,数据共享简单,但是同步复杂。
(2)进程单独占有一定的内存地址空间,一个进程出现问题不会影响其他进程,不影响主程序的稳定性,可靠性高;一个线程崩溃可能影响整个程序的稳定性,可靠性较低。
(3)进程单独占有一定的内存地址空间,进程的创建和销毁不仅需要保存寄存器和栈信息,还需要资源的分配回收以及页调度,开销较大;线程只需要保存寄存器和栈信息,开销较小。
(4)进程是操作系统进行资源分配的基本单位(进程之间互不干扰),而线程是操作系统进行调度的基本单位(线程间互相切换)。
他们的区别关键是看是否单独占有一定的内存地址空间
线程概述
线程的定义和分类
首先系统中资源分配和资源调度的基本单位叫做进程,一个进程包含多个线程,每个线程也可以得到一小段程序的执行时间,这样一个进程就可以具有多个并发执行的线程。
多线程的优缺点
- 多线程的优点
要提高对用户的响应速度,使用多线程是一种最有效的方式,在具有一个处理器的计算机上,多线程可以通过利用用户事件之间很小的时间段在后台处理数据来达到这种效果。
(1)通过网络与WEB服务器和数据库进行通信
(2)执行大量占用时间的操作
(3)区分具有不同优先级的任务
(4)使用户界面可以在将时间分配给后台任务时仍能快速作出反映。 - 多线程的缺点
建议不要在程序中使用太多的线程,这样可以最大限度的减少操作系统资源的使用,并提高性能。
(1)系统将为进程和线程所需的上下文信息使用内存。因此,可以创建的进程和线程的数目受内存的限制
(2)跟踪大量的线程将占用大量的处理器时间。如果线程过多,则其中大多数线程都不会产生明显的进度。如果大多数线程处于一个进程中,则其他进程中线程的调度频率就会很低。
(3)使用多个线程控制代码执行非常复杂,并可能产生许个BUG。
(4)销毁线程需要了解可能发生的问题并进行处理。
线程实现
使用Thread类创建线程
Thread类位于System.Thread命名空间下,该类主要用于创建并控制线程、设置程序优先级并获取其状态。创建线程需要使用Thread类的构造函数。语法如下:
public Thread(ThreadStart start)
public Thread(ParameterizedThreadStart strat)
参数start表示线程开始时要调用的方法。
Thread类的常用属性及说明。
(1)ApartmenState:获取或设置此线程的单元状态
(2)CurrentContext:获取线程正在其中执行的当前上下文
(3)CurrentThread:获取当前正在运行的线程
(4)IsAlive:获取一个值,该值指示当前线程的执行状态
(5)ManagedThreadId:获取当前托管线程的唯一标识符
(6)Name:获取或设置线程的名称
(7)Priority:获取或设置一个值,该值指示线程的调度优先级
(8)ThreadState:获取一个值,该值包含当前线程的状态。
方法:
(1)Abort:在调用此方法的线程上引发ThreadAbortException,以开始终止此线程的过程。调用此方法通常会终止线程。
(2)Join:阻塞调用线程,直到某个线程终止或经过指定时间为止
(3)Sleep:将当前线程挂起/阻塞指定的时间
(4)SpinWait:导致线程等待由interation参数定义的时间量
(5)Start:开始执行线程
案例:向右移动图标
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
CheckForIllegalCrossThreadCalls = false;//使线程可以调用窗体控件
}
int x = 12;
void Roll()
{
while (x <= 260)
{
pictureBox1.Location = new Point(x, 12);
Thread.Sleep(80);
x += 4;
if (x >= 260)
{
x = 12;
}
}
}
private void Form1_Load(object sender, EventArgs e)
{
Thread th = new Thread(new ThreadStart(Roll));//创建线程对象
th.Start();//启动线程
}
}
线程的生命周期
操作线程的方法
线程的休眠
线程的休眠主要通过Thread类的sleep方法实现,该方法用来将当前线程阻塞指定的时间,他有2种重载形式。
Thread.Sleep(1000); //使线程休眠1秒
线程的加入
假如当前程序为多线程程序且存在一个线程A,现在需要插入线程B,并要求线程B执行完毕后,再继续执行线程A,此时可以使用Thread类的Join方法来现实。
Join方法用来阻塞调用线程,直到某个线程终止时为止,他有3种重载方式。
(1)阻塞调用程序,直到某个线程终止为止
Public void Join()
(2)阻塞调用程序,直到某个线程终止或经过了指定时间为止
Public bool Join(int millisecondsTimeout)
// millisecondsTimeout等待线程终止的毫秒数
//返回值:如果线程终止,则为TRUE;如果线程在经过了millisecondsTimeout参数指定的时间后未终止,则为false
(3)阻塞调用程序,直到某个线程终止或经过了指定时间为止
Public bool Join(TimeSpan timeout)
// Timeout等待线程终止的TimeSpan
//返回值:如果线程终止,则为TRUE;如果线程在经过了Timeout参数指定的时间后未终止,则为false
案例"控制2个进度条的滚动”
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
CheckForIllegalCrossThreadCalls = false;//使线程可以调用窗体控件
}
Thread th1, th2;//分别控制进度条1和进度条2
void Pro1()
{
int count = 0; //标识何时加入线程2
while (true)
{
progressBar1.PerformStep();//设置进度条的当前值
count += progressBar1.Step;//标识自增
Thread.Sleep(100);
if (count == 20)
{
th2.Join();//使线程2调用Join方法
}
}
}
void Pro2()
{
int count = 0; //标识何时加入线程2
while (true)
{
progressBar2.PerformStep();//设置进度条的当前值
count += progressBar2.Step;//标识自增
Thread.Sleep(100);
if (count == 100)
break;
}
}
private void Form1_Load(object sender, EventArgs e)
{
th1 = new Thread(new ThreadStart(Pro1));//创建线程1对象
th1.Start();//启动线程1
th2 = new Thread(new ThreadStart(Pro2));
th2.Start();//启动线程1
}
}
线程的终止
终止线程使用Thread类的Abort方法实现,该方法有两种重载形式
(1)终止线程,在调用此方法的线程上引发ThreadAbortException异常,以开始终止此线程的过程
Public void Abort()
(2)终止线程,在调用此方法的线程上引发ThreadAbortException异常,以开始终止此线程并提供有关线程终止的异常信息的过程。
public void Abort (Object stateInfo)//一旦线程被终止,它将无法重新启用
线程的优先级
说明:多线程的执行本身就是多个线程的交换执行,并非同时执行,通过设置线程优先级的高低,只是说明该线程会优先执行或者暂不执行的概率更大一些,并不保证优先级高的线程就一定比优先级低的线程先执行。
- Highest 可以将Thread安排在具有任何其他优先级的线程之前。
- AboveNormal 可以将Thread安排在具有 Highest 线程之后
- Normal
- BelowNormal
- Lowest
开发人员可以通过访问线程的Priority属性来获取和设置其优先级。Priority属性用来获取或设置一个值,该值指示线程的调度优先级,语法如下:
Public ThreadPriority Priority {get;set} //默认为Normal
例:将上述“控制2个进度条”线程1的优先级设置为最低,线程2的优先级设置为最高,运行观察。
private void Form1_Load(object sender, EventArgs e)
{
th1 = new Thread(new ThreadStart(Pro1));//创建线程1对象
th1.Priority = ThreadPriority.Lowest;
th1.Start();//启动线程1
th2 = new Thread(new ThreadStart(Pro2));
th2.Priority=ThreadPriority.Highest;
th2.Start();//启动线程1
}
线程的同步
线程同步是指并发线程高效、有序的访问共享资源所采用的技术,所谓同步,是指某一时刻只有一个线程可以访问资源,只有当资源所有者主动放弃了代码或资源的所有权时,其他线程才可以使用这些资源,分别使用到Lock关键字、Monitor类、Mutex类实现。
举例,火车站售票系统
class Program
{
int num = 10;
void Ticket()
{
while (true)
{
if (num > 0)
{
Thread.Sleep(100);
Console.WriteLine(Thread.CurrentThread.Name + ".....票数" + num--);
}
}
}
static void Main(string[] args)
{
Program p = new Program();
Thread tA = new Thread(new ThreadStart(p.Ticket));
tA.Name = "线程一";
Thread tB = new Thread(new ThreadStart(p.Ticket));
tB.Name = "线程二";
Thread tC = new Thread(new ThreadStart(p.Ticket));
tC.Name = "线程三";
Thread tD = new Thread(new ThreadStart(p.Ticket));
tD.Name = "线程四";
tA.Start();
tB.Start();
tC.Start();
tD.Start();
Console.ReadLine();
}
}
票数出现负数就是资源共享出现问题,这时候需要给共享资源上一把锁,这就是程序开发中的线程同步。所谓同步,是指某一时刻只有一个线程可以访问资源,只有当资源所有者自动放弃了代码或者资源的所有权时,其他线程才可以使用这些资源。
1.使用lock关键字实现线程同步
class Program
{
int num = 10;
void Ticket()
{
while (true)
{
lock (this) //锁定代码块,以便线程同步
{
if (num > 0)
{
Thread.Sleep(100);
Console.WriteLine(Thread.CurrentThread.Name + ".....票数" + num--);
}
}
}
}
static void Main(string[] args)
{
Program p = new Program();
Thread tA = new Thread(new ThreadStart(p.Ticket));
tA.Name = "线程一";
Thread tB = new Thread(new ThreadStart(p.Ticket));
tB.Name = "线程二";
Thread tC = new Thread(new ThreadStart(p.Ticket));
tC.Name = "线程三";
Thread tD = new Thread(new ThreadStart(p.Ticket));
tD.Name = "线程四";
tA.Start();
tB.Start();
tC.Start();
tD.Start();
Console.ReadLine();
}
}
2.使用Monitor类实现线程同步
Monitor类提供了同步对象的访问机制,他通过向单个线程授予对象锁来控制对对象的访问,对象锁提供限制访问代码块的能力。当一个线程拥有对象锁时,其他任何线程都不能获取该锁。
在上述案例中只需更改Program
class Program
{
int num = 10;
void Ticket()
{
while (true)
{
Monitor.Enter(this) ; //锁定代码块,以便线程同步
if (num > 0)
{
Thread.Sleep(100);
Console.WriteLine(Thread.CurrentThread.Name + ".....票数" + num--);
}
Monitor,Exit(this); //解锁代码块
}
}
3.使用Mutex类实现线程同步
//首先说明,尽管Mutex类可以用于进程内的线程同步,但是使用Monitor类通常更为可取,因为Monitor监视器是专门为.NET Framework而设计的,因而他可以更好的利用资源。相比之下,Mutex类是WIN32构造的包装,尽管Mutex类比监视器更为强大,但是相对于Monitor类,他所需的互操作转换更消耗计算机资源。
线程池https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.threadpool?view=netframework-4.7.2