前言
我们知道windows是基于多任务的操作系统,我们可以使用计算机一边听音乐,一边用qq聊天。而这些活动完全可以同时进行,这种思想在C#叫做并发,而将并发完成的每一件事我们称为线程。
说到线程,我们不得不提进程,进程是系统中资源分配和资源调度的基本单位。举个例子,我们在windows操作系统中同时按ctrl+alt+delete键,打开任务资源管理器,就能看到系统正在运行的进程。其实每个独立执行的程序就是一个进程。比如QQ、微信、谷歌和迅雷等等。那么进程和线程是什么关系呢?每个进程可以同时包含多个线程。我们打开一个进程,这个进程有很多功能,能够互不干扰、独立地运行,这些就是线程的并发机制。而线程的并发执行机制得益于系统分配给每个进程一段有限使用CPU的时间(也称CPU时间片)。由于CPU转换较快,我们看起来每个进程好像是在同时执行一样。线程与进程的关系可如下图所示:
线程基本知识点
- 创建执行线程
创建线程需要使用Thread类的构造函数
public Thread(ThreadStart strat)
public THread(ParameterizedThreadStart start)
执行线程需要使用Start方法
public void Start()
public void Start(Object parameter)
这里举一个例子:向右移动的飞机图标
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(500); //使线程休眠0.5s
x += 4;
if (x >= 260)
{
x = 12;
}
}
}
private void Form1_Load(object sender, EventArgs e)
{
Thread th = new Thread(new ThreadStart(Roll)); //创建线程对象
th.Start();
}
}
运行结果如下图示:
- 线程的生命周期(★)
万事万物有始有终,线程也不例外,线程也有自己的生命周期,C#中线程包括5个状态:出生,就绪,运行,暂停(阻塞),死亡,如下图示:
注意:当线程的暂停状态解除后,线程不是进入运行状态,而是返回到就绪状态,继续等待CPU时间片资源。
- 操作线程的方法
线程的休眠:
public static void Sleep(int millisecondsTimeOut)
public static void Sleep(TimeSpan timeout)
这里举一个例子: 模拟红绿灯变化场景
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
CheckForIllegalCrossThreadCalls = false;
}
void ControlLight()
{
this.pictureBox1.Image = global::WindowsFormsApp15._3.Properties.Resources.Green;
while (true) //线程始终处于被启用状态
{
Thread.Sleep(5000); //线程休眠5秒
this.pictureBox1.Image = global::WindowsFormsApp15._3.Properties.Resources.Yellow;
Thread.Sleep(2000); //线程休眠2秒
this.pictureBox1.Image = global::WindowsFormsApp15._3.Properties.Resources.Red;
Thread.Sleep(8000); //线程休眠8秒
this.pictureBox1.Image = global::WindowsFormsApp15._3.Properties.Resources.Green;
}
}
private void Form1_Load(object sender, EventArgs e)
{
Thread th = new Thread(new ThreadStart (ControlLight));
th.Start();
}
}
运行结果如下图所示:
(但是实际上这里是个单线程的程序,通过对线程执行Sleep方法使其休眠实现红绿灯交替变化。)
-线程的加入
public void Join ()
public bool Join (int millisecondTimeout)
public bool Join (TimeSpan timeout)
当某个线程使用Join方法加入到另一个线程时,另一个线程会等待该线程执行完毕后再继续执行。
- 线程的终止
public void Abort ( )
public void Abort ( )
举个例子:控制进度条的滚动
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
CheckForIllegalCrossThreadCalls = false; //允许线程调用窗体控件
}
//分别定义两个线程控制进度条1,2
Thread th1, th2;
void Pro1()
{
int count = 0; //标识何时加入线程2
while (true)
{
progressBar1.PerformStep(); //设置进度条的当前值
count += progressBar1.Step; //标识自增
Thread.Sleep(100);
if (count == 20)
{
th2.Join();
}
}
}
void Pro2()
{
int count = 0;
while (true)
{
progressBar2.PerformStep();
count += progressBar2.Step;
Thread.Sleep(100);
if (count == 100)
{
break;
}
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (th1.ThreadState == ThreadState.Running)
th1.Abort();
if (th2.ThreadState == ThreadState.Running)
th2.Abort();
}
private void Form1_Load(object sender, EventArgs e)
{
th1 = new Thread(new ThreadStart(Pro1));
th2 = new Thread(new ThreadStart(Pro2));
//th1.Priority = ThreadPriority.Lowest;
th1.Start();
//th2.Priority = ThreadPriority.Highest;
th2.Start();
}
}
运行结果如图所示:
线程的同步
实际开发中,应用多线程的情况有很多,比如车站售票系统,排队叫号系统等等。我们再编写多线程程序时,应该考虑多线程的安全问题,因为多线程程序涉及到多个线程对单一对象的数据进行读取操作,可能会造成数据紊乱。所以我们需要多线程的同步机制来保证多线程的安全问题。
举个例子,如果我们没有考虑到线程的安全问题,直接模拟火车站售票系统的功能。
class Program
{
int num = 10; //设置当前总票数
void TicketInfo()
{
while (true)
{
//lock (this)
{
if (num > 0)
{
//lock (this) 放在这里无法让线程同步 sure 因为造成线程不同步的原因就是多个线程对单一对象的数据访问造成的,放在这里就无法判断了
Thread.Sleep(100); //线程休眠100毫秒
Console.WriteLine(Thread.CurrentThread.Name + "---票数" + num--);
}
}
}
}
static void Main(string[] args)
{
Program p = new Program();
Thread th1 = new Thread(new ThreadStart(p.TicketInfo));
Thread th2 = new Thread(new ThreadStart(p.TicketInfo));
Thread th3 = new Thread(new ThreadStart(p.TicketInfo));
Thread th4 = new Thread(new ThreadStart(p.TicketInfo));
th1.Name = "线程一";
th2.Name = "线程二";
th3.Name = "线程三";
th4.Name = "线程四";
th1.Start();
th2.Start();
th3.Start();
th4.Start();
Console.ReadLine();
}
}
运行结果如下图示:
我们可以看到当线程一执行时,还未来得及做递减操作,程序指定它调用Sleep方法进入暂停状态,这时线程二、三、四也都开始执行,发现num等于1仍然大于零,但是此时线程一休眠时间到对num执行递减操作,同时线程二、三、四也会对num执行递减操作,从而产生负值。
所以怎么解决资源共享的问题呢?类比人类上厕所会关门,我们可以再共享资源上面加一把锁,来执行读取操作。这就是多线程的同步。
多线程的同步有以下几个方法:
-
使用lock关键字实现线程的同步
lock关键字可以用来确保代码块完成运行,而不会被其他线程影响,实际上它是通过再代码块运行期间为给定对象获取互斥锁来实现的。
Object thislock = new Object();
lock (thislock)
{
//要加锁运行的代码块
} -
使用Monitor类来实现线程同步
Monitor类的两个方法:
Enter():在指定对象上获取排他锁
Exit():释放指定对象上的排他锁 -
使用Mutex类实现线程同步
使用Mutex类实现线程同步时,首先要实例化一个Mutex对象
public Mutex(bool initallyOwned) -
Mutex类的两个方法:
WaitOne():阻塞当前进程
ReleseMutex():释放Mutex一次
class Program
{
int num = 10; //设置当前总票数
void TicketInfo()
{
while (true)
{
//①lock (this) //this 指当前类的对象
//②Monitor.Enter(this)
//③Mutex myMutex =new Mutex(this) //创建Mutex对象
//②myMutex.WaitOne();//阻塞当前进程
{
if (num > 0)
{
//lock (this) 放在这里无法让线程同步 sure 因为造成线程不同步的原因就是多个线程对单一对象的数据访问造成的,放在这里就无法判断了
Thread.Sleep(100); //线程休眠100毫秒
Console.WriteLine(Thread.CurrentThread.Name + "---票数" + num--);
}
}
//②Monitor.Exit(this)
//③myMutex.ReleaseMutex(); //释放Mutex对象
}
}
static void Main(string[] args)
{
Program p = new Program();
Thread th1 = new Thread(new ThreadStart(p.TicketInfo));
Thread th2 = new Thread(new ThreadStart(p.TicketInfo));
Thread th3 = new Thread(new ThreadStart(p.TicketInfo));
Thread th4 = new Thread(new ThreadStart(p.TicketInfo));
th1.Name = "线程一";
th2.Name = "线程二";
th3.Name = "线程三";
th4.Name = "线程四";
th1.Start();
th2.Start();
th3.Start();
th4.Start();
Console.ReadLine();
}
}
以上3种方法选择其中之一即可都可以实现线程的同步。运行结果如下图(我这里选择第一种使用lock关键字):
线程池
如果需要大量创建短生命周期的线程,我们可以考虑使用线程池的技术——ThreadPool类。举个例子,我们在生活中使用的共享单车,用完的话直接锁好放在停车区内,后面有人需要的直接扫码骑走即可,这个停车区就相当于线程池。同样地,我们创建了大量小线程,当使用完后我们放到线程池中,如果后面需要线程执行功能的话,我们直接取出执行即,不用重新新建线程,可以大大方提升线程的执行效率。
码字不易,如果觉得对你有帮助,求个赞哦!如果有不正确的地方,欢迎大家来交流!
(参考:零基础学C#)