线程
讲多线程之前,我们先讲线程。
C#中的线程类叫Thread,主要用来创建或控制线程。
先上个例子
static void Main(string[] args)
{
Thread thread = new Thread(ThreadTest);
//设置线程名称
thread.Name = "测试线程";
//调用Start方法启动线程
thread.Start();
Console.ReadKey();
}
public static void ThreadTest()
{
Console.WriteLine("Name:"+Thread.CurrentThread.Name);
}
new了一个线程,传入了一个委托参数ThreadTest()。
然后给线程设置了一个名称
最后调用线程的Start()方法,启动线程。
委托方法中输出了线程的名称。
多线程,其核心价值是为了在同一时间段内完成多个任务。
结合现实场景来看,就是火车站售票厅的售票窗口了。如果只有一个售票窗口,以我国人口基数来看,显然是不现实的。所以,多个售票窗口就满足多线程的概念,同一个时间段,处理多个任务。
多线程也是有缺点的,每开辟一个新的线程,势必要占用一定的内存空间,开辟的越多,占用的内存也就越多。
线程的概念基本上大家应该了解了,接下来通过实际例子来深入了解线程在实际操作过程中可能遇到的问题。
例子
我们用之前说到的火车站售票窗口,来讲多线程的第一个问题。并发访问共享数据问题。假如,一个车次有100张可售票。有10个窗口同时售卖这100张票,那么此时的"10个窗口同时售卖"就是并发访问,"100张票"就是共享数据。在现实中,显然不能因为有多个人在卖,就出现第101张票,甚至更多票的情况。
我们先模拟一个多线程售票的代码。
Ps*本例子分为多个代码片段,每个片段都在讲一个知识点!!!!所有的例子我都在模拟新手了解多线程的思路去编写。懂的人可以跳过,不懂的朋友请耐心看每一个例子的代码,以及分析。
例子1
//总票数。
public static int TicketsCount = 100;
static void Main(string[] args)
{
for (int i = 1; i <= 10; i++) {
Thread thread = new Thread(SellTicket);
//设置线程名称
thread.Name = "售票窗口["+i+"]";
//调用Start方法启动线程
thread.Start();
}
Console.ReadKey();
}
//售票方法
public static void SellTicket()
{
TicketsCount -= 1;
Console.WriteLine(Thread.CurrentThread.Name + ":我卖了一张票::"+ TicketsCount);
}
根据题干,我在例子1中,定义了一个变量TicketsCount来存储总票数。然后我使用for循环创建了10个线程,用来模拟10个不同的售票窗口,且每个线程都调用SellTicket()售票方法,方法中每次调用会让总票数减少1,用来模拟售票效果。然后我们运行查看结果:
不对呀,10个窗口,只卖了10张票,然后就没有卖了?回过头看代码,找到问题了。因为线程需要明确的指示去执行动作,如果你没有给与线程明确的指示动作,他在完成以后会自动停止。而我们的SellTicket()售票方法中,只是进行了一次售卖,就没有下一步指示了,于是所有的售票窗口在售卖了一次票以后,就自动停止了。根据上述结论,我们来改造例子2
例子2
//售票方法
public static void SellTicket()
{
while (TicketsCount > 0)
{
TicketsCount -= 1;
Console.WriteLine(Thread.CurrentThread.Name + ":我卖了一张票::" + TicketsCount);
}
}
例子2中,我使用while循环,改造了SellTicket()售票方法。之前没讲过while循环,这里讲一下。while关键字后面跟着的就是条件语句,用来判断循环内的语句在什么情况下执行,我这里的条件是,如果总票数大于0,则继续售卖。总票数是100,每次售卖总票数都会减1,一直到没有票了,就不再执行。然后我们看下运行效果:
这次看起来,好像是对了,有多个窗口在同时售票,且每次售卖总票数都减少了。直到总票数为0的时候,确实停止售票了。但是,现在数据量小,只有100次操作,我们还可以逐条去对比他的正确性,那么,如果有成千上万条数据的时候,我们又如何去对比数据的正确性呢?最简单的第一思维,就是建立一个计数器了,用一个变量来存储售票的次数,最终停止售票时,我们输出一下计数器的值,看看是否是正确的。
例子3
public static int SellCount = 0;
//售票方法
public static void SellTicket()
{
while (TicketsCount > 0)
{
SellCount += 1;
TicketsCount -= 1;
Console.WriteLine(Thread.CurrentThread.Name + ":我卖了一张票::" + TicketsCount);
}
Console.WriteLine("最终售票:" + SellCount);
}
我定义了一个SellCount变量,用来记录售票次数,SellTicket()方法在被调用执行售票时,每次对SellCount变量进行+1操作。然后我们看看运行结果:
对了,没有问题,最终售票确实是100张。而之所以执行了10次最终售票打印是因为每个窗口在其售票完成后,都会输出一次。
但是!!!多线程仅此而已吗?出现这种结果其实很有可能是电脑的运算速度快,所以看起来没有延迟计算,最终其售票正确了,而在现实场景中,某个售票窗口,是很容易被其他的因素扰乱售票的进程的(比如顾客问价、网络延迟)。所以我们为了模拟被其他因素影响售票的效果,我们使用线程休眠的动作,让每个窗口在每次售票时都停顿100毫秒看看。
例子3如果你多次重复执行,你也有可能看到错误,前提是你电脑足够慢
例子4
//售票方法
public static void SellTicket()
{
while (TicketsCount > 0)
{
Thread.CurrentThread.Join(100);
SellCount += 1;
TicketsCount -= 1;
Console.WriteLine(Thread.CurrentThread.Name + ":我卖了一张票::" + TicketsCount);
}
Console.WriteLine("最终售票:" + SellCount);
}
注意例子4,我在售票和计数之前,使用
Thread.CurrentThread.Join(100);
让当前线程阻塞了100毫秒,模拟在售票过程中遇到的其他因素,再来看看运行结果:
很显然,在例子4中。出现了额外售票的情况。这如果发生在现实中,那就乱套了。所以,多个线程在访问共享的数据时,会因为一些额外因素出现一些数据BUG,所以在程序中,针对多线程的运行,我们需要有其他的办法来控制线程,从而避免这种错误的产生。