一、基本概念
进程(Process)
在操作系统中正在运行的应用程序被视为一个进程,包含着一个运行程序所需要的资源,进程可以包括一个或多个线程 。
线程(Thread)
进程的基本执行单元,是操作系统分配CPU时间的基本单位 ,在进程入口执行的第一个线程被视为主线程 。
(1)线程是一个可执行路径,它可以独立于其它线程执行。
(2)每个线程都在操作系统的进程(Process)内执行,而操作系统进程提供了程序运行的独立环境。
(3)单线程应用:在进程的独立环境里只跑一个线程,所以该线程拥有独占权。
(4)多线程应用:单个进程中会跑多个线程,它们会共享当前的执行环境(尤其是内存)。
线程属性
线程生命周期
线程生命周期开始于 System.Threading.Thread 类的对象被创建时,结束于线程被终止或完成执行时。
下面列出了线程生命周期中的各种状态:
多线程的优缺点
-
多线程优点:可以同时完成多个任务;可以让占用大量处理时间的任务或当前没有进行处理的任务定期将处理时间让给别的任务;可以随时停止任务;可以设置每个任务的优先级以优化程序性能。
-
多线程的缺点:
(1) 内存占用 线程也是程序,所以线程需要占用内存,线程越多,占用内存也越多(每个线程都需要开辟堆栈空间,多线程时有时需要切换时间片)。
(2) 管理协调 多线程需要协调和管理,所以需要占用CPU时间以便跟踪线程,线程太多会导致控制太复杂。
(3)资源共享 线程之间对共享资源的访问会相互影响,必须解决争用共享资源的问题。
二、编程实战
1、创建多线程
(1)为主线程命名为"主线程",并创建一个名为“子线程”的线程。
(2)使用.Start()方法开启子线程。
(3)在主、子线程中分别执行PrintNumbers()方法。
static void Main(string[] args)
{
//设置主线程名
Thread.CurrentThread.Name = "主线程";
//开启子线程,并设置名
Thread thread = new Thread(PrintNumbers);
thread.Name = "子线程";
thread.Start();
//主线程中调用PrintNumbers()方法
PrintNumbers();
Console.ReadKey();
}
/// <summary>
/// 输出数字
/// </summary>
static void PrintNumbers()
{
var threadName = Thread.CurrentThread.Name;
Console.WriteLine("当前线程:" + threadName);
for (int i = 0; i < 10; i++)
{
Console.WriteLine(threadName + ":" + i);
}
}
(4)可看到主、子线程交替打印0-9之间的数字(注:不同的电脑打印出来的顺序不一样,可多次执行程序,观察异同),如下图:
2、线程暂停
(1)PrintNumbers()方法表示正常输出0-9之间的数字,PrintNumbersWithDelay()方法表示每次输出数字前都让当前线程先“睡(暂停)”1秒,然后再输出数字。
(2) Thread.Sleep()是用来执行线程暂停的方法,在主线程中,则是暂停主线程,子线程中则暂停子线程。
(3)在主线程中调用PrintNumbers()方法正常输出,子线程中调用PrintNumbersWithDelay()方法延迟输出。
static void Main(string[] args)
{
//设置主线程名
Thread.CurrentThread.Name = "主线程";
//开启子线程,并设置名
Thread thread = new Thread(PrintNumbersWithDelay);
thread.Name = "子线程";
thread.Start();
//主线程中调用PrintNumbers()方法
PrintNumbers();
Console.ReadKey();
}
/// <summary>
/// 输出数字
/// </summary>
static void PrintNumbers()
{
var threadName = Thread.CurrentThread.Name;
Console.WriteLine("当前线程:" + threadName);
for (int i = 0; i < 10; i++)
{
Console.WriteLine(threadName + ":" + i);
}
}
/// <summary>
/// 延迟输出数字
/// </summary>
static void PrintNumbersWithDelay()
{
var threadName = Thread.CurrentThread.Name;
Console.WriteLine("当前线程:" + threadName);
for (int i = 0; i < 10; i++)
{
Thread.Sleep(TimeSpan.FromSeconds(1));//每次都暂停1秒再执行输出操作
Console.WriteLine(threadName + ":" + i);
}
}
(4)结果如下图所示:
3、线程等待
(1)PrintNumbersWithDelay()方法表示每次输出数字前都让当前线程先“睡(暂停)”1秒,然后再输出数字。
(2)使用线程的Join()方法,让主线程等待子线程执行完。
static void Main(string[] args)
{
//设置主线程名
Thread.CurrentThread.Name = "主线程";
//开启子线程,并设置名
Thread thread = new Thread(PrintNumbersWithDelay);
thread.Name = "子线程";
thread.Start();
thread.Join();//线程等待,等待子线程执行完后,才会继续往下执行
Console.WriteLine(Thread.CurrentThread.Name+"执行结束。。。");
Console.ReadKey();
}
/// <summary>
/// 延迟输出数字
/// </summary>
static void PrintNumbersWithDelay()
{
var threadName = Thread.CurrentThread.Name;
Console.WriteLine("当前线程:" + threadName);
for (int i = 0; i < 10; i++)
{
Thread.Sleep(TimeSpan.FromSeconds(1));//每次都暂停四秒再执行输出操作
Console.WriteLine(threadName + ":" + i);
}
}
(3)如下图,当子线程输出完才输出“主线程执行结束。。。”,如果将join去掉,将会出现先输出“主线程执行结束。。。”再输出子线程中的数字。
4、线程终止
(1)创建子线程调用方法循环打印数字。
(2)让主线程等待6秒后,调用Abort()方法让线程终止。
static void Main(string[] args)
{
Console.WriteLine("Starting program...");
Thread t = new Thread(PrintNumbersWithDelay);
t.Start();
Thread.Sleep(TimeSpan.FromSeconds(6));
t.Abort();
Console.WriteLine("A thread has been aborted");
}
static void PrintNumbersWithDelay()
{
Console.WriteLine("Starting...");
for (int i = 1; i < 10; i++)
{
Thread.Sleep(TimeSpan.FromSeconds(2));
Console.WriteLine(i);
}
}
(3)可看到如下图所示的异常,提示当前平台不支持使用Abort(net core3.0),故不支持使用Abort()方法来终止线程,后续会介绍CancellationToken来取消线程的执行。
5、检测线程状态
(1)使用线程的ThreadState打印当前线程的状态,ThreadState是一个枚举值。
(2)创建一个子线程,并在子线程中使用 Thread.Sleep(TimeSpan.FromSeconds(1))让每次打印都先暂停一秒。
(3)主线程中循环十次去打印子线程的状态。
(4)主线程中暂停5秒后,调用Join()方法,让主线程等待子线程打印完成后再继续执行。
//ThreadState枚举值如下:
//Running = 0,
//StopRequested = 1,
//SuspendRequested = 2,
//Background = 4,
//Unstarted = 8,
//Stopped = 16,
//WaitSleepJoin = 32,
//Suspended = 64,
//AbortRequested = 128,
//Aborted = 256
static void Main(string[] args)
{
//设置主线程名
Thread.CurrentThread.Name = "主线程";
Console.WriteLine(Thread.CurrentThread.Name + ":" + Thread.CurrentThread.ThreadState.ToString());
Thread t = new Thread(PrintNumbersWithStatus);
t.Name = "子线程";
t.Start();
for (int i = 0; i < 10; i++)
{
Console.WriteLine("主线程中打印子线程状态,当前子线程:{0},其状态为:{1}", t.Name, t.ThreadState.ToString());
}
Thread.Sleep(TimeSpan.FromSeconds(5));
t.Join();
Console.WriteLine(t.Name + ":" + t.ThreadState.ToString());
Console.WriteLine("执行结束。。。");
Console.ReadKey();
}
/// <summary>
///
/// </summary>
static void PrintNumbersWithStatus()
{
for (int i = 0; i < 5; i++)
{
Thread.Sleep(TimeSpan.FromSeconds(1));
Console.WriteLine(Thread.CurrentThread.Name + ":" + i);
}
}
(5)如下图,可看到子线程的状态变化为"Running"->“WaitSleepJosin”->“Stopped”
6、线程优先级
(1)创建ThreadSample类,其中包含CountNumbers()、stop()方法以及counter和_isStopped字段。
(2)在不限定CPU核数的情况下,设置线程一为高优先级,线程二为低优先级,可看到优先级高的在两秒内计算的次数比优先级低的多了几百万。
(3)使用“ Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(1)”限定CPU核数为1核后,优先级低的线程无法进行任何计算操作,因其资源都被线程高的掠夺去了。
(4)此例子可看出优先级高的线程拥有的计算机资源会更多!
//线程优先级枚举值如下:
//Lowest = 0,
//BelowNormal = 1,
//Normal = 2,
//AboveNormal = 3,
//Highest = 4
static void Main(string[] args)
{
Console.WriteLine("当前线程优先级(priority): {0}", Thread.CurrentThread.Priority);
Console.WriteLine("不限定计算机处理器核数进行计算与输出!");
RunThreads();//不限定处理器核数,并开启线程
Thread.Sleep(TimeSpan.FromSeconds(2));
Console.WriteLine("设置当前为单核处理器并进行计算与输出!");
Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(1);
RunThreads();//限定为单核处理器并进行计算与输出!
}
/// <summary>
/// 开启两个线程,并调用计数器
/// </summary>
static void RunThreads()
{
var sample = new ThreadSample();
var threadOne = new Thread(sample.CountNumbers);
threadOne.Name = "线程一";
var threadTwo = new Thread(sample.CountNumbers);
threadTwo.Name = "线程二";
//设置线程一优先级级别为最高
threadOne.Priority = ThreadPriority.Highest;
//设置线程二优先级级别为最低
threadTwo.Priority = ThreadPriority.Lowest;
threadOne.Start();
threadTwo.Start();
//线程暂停两秒
Thread.Sleep(TimeSpan.FromSeconds(2));
//停止子线程的计算操作
sample.Stop();
}
/// <summary>
/// 线程优先级
/// </summary>
class ThreadSample
{
//定义一个暂停标识
private bool _isStopped = false;
//设置暂停的方法
public void Stop()
{
_isStopped = true;
}
/// <summary>
/// 数值统计并输出当前线程名与线程优先级
/// </summary>
public void CountNumbers()
{
//计数器
long counter = 0;
while (!_isStopped)
{
counter++;
}
//输出当前线程名+线程优先级+计数器最终结果
Console.WriteLine("{0} with {1,11} priority " +
"has a count = {2,13}", Thread.CurrentThread.Name,
Thread.CurrentThread.Priority,
counter.ToString());
}
}
(5)执行结果如下:
以上代码及思路参考的是“多线程编程实战第一章”。
源码:https://github.com/xgysigned/C-Sharp-Thread
续篇:C#多线程基础(二) PS:阅读C#多线程编程实战第一章总结
持续更新中。。。。。