CS4L18——多线程

本章代码关键字

1
2
3
4
5
6
7
System.Threading        // 线程相关命名空间
Thread // 线程
thread.Start() // 开启线程
thread.IsBackground // 设置线程为后台线程,当主线程执行完毕时,后台线程会被自动关闭
thread.Abort() // 中止线程,此方法已废弃,不建议使用
Thread.Sleep() // 让线程休眠一段时间(传入的单位是毫秒)
lock() {} // 给对象加锁,确保同时只有一个线程能够访问该对象

进程

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动。是系统进行资源分配和调度的基本单位,是操作系统结构的基础

说人话:打开一个应用程序就是在操作系统上开启了一个进程
进程之间可以相互独立运行,互不干扰,进程之间也可以相互访问、操作

进程图片说明

线程

操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位
一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,我们目前写的程序 都在主线程中

简单理解线程:就是代码从上到下运行的一条“管道”

多线程

我们可以通过代码,开启新的线程,以同时运行代码的多条 “管道”,就叫多线程

多线程图片

多线程是多个可以同时执行代码逻辑的“管道”,可以通过代码开启多线程,用多线程处理一些复杂的可能影响主线程流畅度的逻辑

多线程语法相关

C# 提供了一个线程类 Thread​,使用前需要先引用 using System.Threading

声明新线程

声明一个新线程,需要提供该线程执行的函数

1
2
3
4
5
6
7
8
9
static void Main()
{
Thread t = new Thread(NewThreadLogic);
}

static void NewThreadLogic()
{
Console.WriteLine("新线程开启");
}

启动线程

使用 Start()​ 方法即可运行创建出来的线程

1
2
3
4
5
6
7
8
9
10
static void Main()
{
Thread thread = new Thread(NewThreadLogic);
thread.Start();
}

static void NewThreadLogic()
{
Console.WriteLine("新线程开启");
}

输出:

1
新线程开启

设置为后台线程

当前台线程都结束了的时候,整个程序也就结束了,即使还有后台线程正在运行
后台线程不会防止应用程序的进程被终止掉,如果不设置为后台线程,可能导致进程无法正常关闭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static void Main()
{
Thread thread = new Thread(NewThreadLogic);
thread.Start();
Thread.Sleep(100); // 设置主线程0.1秒后再继续执行
thread.IsBackground = true; // 如果不设置为后台线程,则thread会阻止进程的关闭
}

static void NewThreadLogic()
{
Console.WriteLine("新线程开启");
while (true)
{
Console.WriteLine("新线程运行中");
}
}

关闭释放一个线程

如果开启的线程中不是死循环,是能够结束的逻辑,那么 不用刻意的去关闭它

如果是死循环,想要中止这个线程,有两种方式:

  1. 死循环中使用 bool​ 标识控制循环是否结束

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    static bool isRuning = true;

    static void Main()
    {
    Thread thread = new Thread(NewThreadLogic);
    thread.Start();
    thread.IsBackground = true; // 如果不设置为后台线程,则thread会阻止进程的关闭
    Console.ReadKey(); // 等待用户输入任意键结束进程
    isRuning = false; // 关闭线程
    Console.ReadKey();
    }

    static void NewThreadLogic()
    {
    Console.WriteLine("新线程开启");
    while (isRuning)
    {
    Console.WriteLine("新线程运行中");
    }
    Console.WriteLine("新线程运行结束");
    }
  2. 通过线程提供的方法 Abort()

    警告!在 .NET 5 及以后的版本,此方法已废弃,使用该方法会抛出错误,因此不建议使用此方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    static void Main()
    {
    Thread thread = new Thread(NewThreadLogic);
    thread.Start();
    thread.IsBackground = true; // 如果不设置为后台线程,则thread会阻止进程的关闭
    Console.ReadKey();
    thread.Abort(); // “Thread.Abort()”已过时:“Thread.Abort is not supported and throws PlatformNotSupportedException.”
    }

    static void NewThreadLogic()
    {
    Console.WriteLine("新线程开启");
    while (true)
    {
    Console.WriteLine("新线程运行中");
    }
    }

线程休眠

让线程休眠一段时间(传入的单位是毫秒,1 秒 = 1000 毫秒),在哪个线程里执行 就休眠哪个线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static bool isRuning = true;

static void Main()
{
Thread thread = new Thread(NewThreadLogic);
thread.Start();
thread.IsBackground = true; // 如果不设置为后台线程,则thread会阻止进程的关闭
Console.ReadKey(); // 等待用户输入任意键结束进程
isRuning = false; // 关闭线程
}

static void NewThreadLogic()
{
Console.WriteLine("新线程开启");
while (isRuning)
{
Thread.Sleep(1000);
Console.WriteLine("新线程运行中");
}
Console.WriteLine("新线程运行结束");
}

线程之间共享数据

多个线程使用的内存是共享的,都属于该应用程序(进程),所以要注意,多线程同时操作同一片内存区域时可能会出问题

假设主线程要在控制台的(0,0)位置画红色三角形,副线程要在(10,5)位置画黄色方形

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Program
{
static bool isRuning = true;

static void Main()
{
Thread thread = new Thread(NewThreadLogic);
thread.Start();
thread.IsBackground = true; // 如果不设置为后台线程,则thread会阻止进程的关闭
Console.Clear();
while (isRuning)
{
Console.SetCursorPosition(0, 0);
Console.ForegroundColor = ConsoleColor.Red;
Console.Write("▲");
}
}

static void NewThreadLogic()
{
while (isRuning)
{
Console.SetCursorPosition(10, 5);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.Write("■");
}
}
}

显示效果:

image

可以看到,由于不同线程间执行顺序不一致,就出现绘制混乱的问题,可以通过加锁的形式避免问题

线程锁

当我们在多个线程当中想要访问同样的东西,进行逻辑处理时,为了避免不必要的逻辑顺序执行的差错,
就需要对某个引用类型对象加锁 lock​,确保同时只有一个线程能够访问该对象

1
2
3
4
lock (引用类型对象)
{
// ...
}

当一个线程运行到 lock​ 内部逻辑时,就会将括号内的引用类型对象是否被锁住,
如果被锁住,就会等待此对象解锁,如果没有被锁住,就会锁住此对象使得其他线程无法进入,然后执行 lock​ 内部的逻辑,执行结束再解锁

这样,就可以跳过线程锁,确保同时只有一个线程能够在控制台输出,这样就不会出现绘制混乱的问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
static bool isRuning = true;
static object lockobj = new object();

static void Main()
{
Thread thread = new Thread(NewThreadLogic);
thread.Start();
thread.IsBackground = true; // 如果不设置为后台线程,则thread会
Console.Clear();
while (isRuning)
{
lock (lockobj)
{
Console.SetCursorPosition(0, 0);
Console.ForegroundColor = ConsoleColor.Red;
Console.Write("▲");
}
}
}

static void NewThreadLogic()
{
while (isRuning)
{
lock (lockobj)
{
Console.SetCursorPosition(10, 5);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.Write("■");
}
}
}

显示效果:

image

多线程对于我们的意义

可以用多线程专门处理一些复杂耗时的逻辑,比如 寻路、网络通信等等
使用多线程来处理这些复杂耗时的逻辑,就可以避免主线程被卡住,导致主线程卡顿等问题