多线程

计算机概念

计算机概念

进程

程序在服务器运行时,占据的计算资源合计,称之为进程。进程之间不会相互干扰,但是进程间的通信比较困难(分布式)。

线程

程序执行的最小单位,相应操作的最小执行流,线程也包含自己的计算资源。线程是属于进程的,一个进程可以有多个线程。

多线程

一个进程里面,有多个线程并发执行

C#

多线程Thread

`Thread`是一个类,就是一个封装,是.NET对线程对象的抽象封装,通过`Thread`去完成的操作,最终是通过像操作系统请求得到的执行流

*   `CurrentThread`:当前线程–任何操作执行都是线程完成的,即获得运行当前这句话的线程
*   `ManagerThreadId`:是.NET平台给Thread起的名字,就是一个`int`值,尽量不重复
*   同步单线程方法:按顺序执行,每次调用完成后才能进下一行,是同一个线程运行的
*   异步多线程方法:发起调用,不等待结果就直接进入下一行(主线程),动作会由一个新线程来执行(子线程)

特点

界面卡顿

*   同步单线程方法卡界面 —- 主(UI)线程线程忙于计算,所以不能相应
*   异步多线程不卡界面 —- 计算任务交给子线程,主(UI)线程已经闲置,可以相应别的操作
*   多线程对于C/S:点击按钮后能不卡死,例如:上传文件界面不卡死
*   多线程对于B/S:例如:用户注册时同时发邮件/发短信/写日志

执行速度

*   同步单线程方法慢 —- 因为只有一个线程在计算
*   异步多线程方法快 —- 因为多个线程并发计算
*   多线程就是用资源换性能
*   但是两者的速度差不是线性增长,例如1个线程耗时1000毫秒,5个线程不代表能做到耗时200毫秒。说明多线程的协调管理由额外的成本,同时资源也是由上限的
*   所以:线程并不是越多越好

无序性

*   启动无序:几乎同一时间向操作系统请求线程,因为线程时操作系统资源,CLR只能去申请,具体时什么顺序启动这个无法掌握
*   执行时间不确定:同个线程同个任务耗时都不一样,更何况多个任务多个线程。这跟操作系统的资源调度策略有关
*   结束无序:上面的都无序,结束时间怎么可能有序

注意

使用多线程时,千万不要通过延时等方式去掌控顺序

多线程控制顺序

异步回调

死循环IsCompleted等待

信号量

EndInvoke获得返回值

使用各个版本的多线程处理方式

Thread

示例:

1
2
3
4
5
6
7
8
9
10
// Thread的API特别丰富,可以玩的很花哨,但是其实大部分人都玩不好,因为线程资源是操作系统管理的,相应并部灵敏,所以没那么好控制  
// Thread启动线程是没限制的,一个for循环启动几百万个线程,分分钟搞死服务器
ThreadStart threadHandler = () =>
{
Console.WriteLine($"Thread Start。。。{Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(2000);
Console.WriteLine($"Thread End。。。{Thread.CurrentThread.ManagedThreadId}");
};
var thread = new Thread(threadHandler);
thread.Start();

ThreadPool

示例:

1
2
3
4
5
6
7
8
9
10
11
// ThreadPool:池化资源管理设计思想,线程是一种资源,以前要用到线程就去申请一个,使用完释放掉。  
// 这样是一种浪费,池化就是一个容器,容器提前申请一批线程,程序需要使用线程,直接找容器获取,用完再放回容器(通过控制资源的状态),避免频繁的申请和销毁,容器自己还会根据限制的数量去申请和释放
// 好处:1.线程服用,2.可以限制最大线程数量
// 缺点:ThreadPool的API太少了,线程等待顺序控制比较弱,影响了实战
WaitCallback callback = o =>
{
Console.WriteLine($"ThreadPool Start。。。{Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(2000);
Console.WriteLine($"ThreadPool End。。。{Thread.CurrentThread.ManagedThreadId}");
};
ThreadPool.QueueUserWorkItem(callback);

Task

示例:

1
2
3
4
5
6
7
8
9
10
// Task:多线程最佳实践  
// 优点:1.Task的线程券是线程池的线程 2.提供了丰富的API,非常适合开发实践
Action action = () =>
{
Console.WriteLine($"Task Start。。。{Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(2000);
Console.WriteLine($"Task End。。。{Thread.CurrentThread.ManagedThreadId}");
};
Task task = new Task(action);
task.Start();

Parallel

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Parallel:并行编程  
// 可以同时执行多个操作,同时主线程也会参与计算,不会闲置。可以节约一个线程
// 可以通过ParallelOptions的MaxDegreeOfParallelism控制最大并发数量
Parallel.Invoke(() =>
{
Console.WriteLine($"Parallel 1 Start。。。{Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(2000);
Console.WriteLine($"Parallel 1 End。。。{Thread.CurrentThread.ManagedThreadId}");
},
() =>
{
Console.WriteLine($"Parallel 2 Start。。。{Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(2000);
Console.WriteLine($"Parallel 2 End。。。{Thread.CurrentThread.ManagedThreadId}");
},
() =>
{
Console.WriteLine($"Parallel 3 Start。。。{Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(2000);
Console.WriteLine($"Parallel 3 End。。。{Thread.CurrentThread.ManagedThreadId}");
});

Task比较全面示例

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public void Coding(string dev, string module)  
{
var random = new Random();
var stopWatch = new Stopwatch();
stopWatch.Start();
Console.WriteLine($"{dev}开发{module}模块 开始.......... ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(random.Next(1000, 5000));
stopWatch.Stop();
Console.WriteLine($"{dev}开发{module}模块 完成.......... ManagedThreadId={Thread.CurrentThread.ManagedThreadId} 共耗时{stopWatch.ElapsedMilliseconds}毫秒");
}

public void AllJob()
{
Console.WriteLine("谈需求...");
Console.WriteLine("选成员...");
Console.WriteLine("分配模块...");

List<Task> tasks = new List<Task>();

tasks.Add(Task.Run(() => Coding("张小三", "用户管理")));
tasks.Add(Task.Run(() => Coding("李筱思", "商品管理")));
tasks.Add(Task.Run(() => Coding("王小五", "订单管理")));
tasks.Add(Task.Run(() => Coding("赵小六", "售后管理")));

TaskFactory taskFactory = new TaskFactory();
taskFactory.ContinueWhenAny(tasks.ToArray(), t =>
{
Console.WriteLine($"达到里程碑... ManagerThreadId={Thread.CurrentThread.ManagedThreadId}");
});
// 等待全部任务完成后,启动一个新的task来完成后续动作
taskFactory.ContinueWhenAll(tasks.ToArray(), tArray =>
{
Console.WriteLine($"项目上线... ManagerThreadId={Thread.CurrentThread.ManagedThreadId}");
Console.WriteLine($"项目验收... ManagerThreadId={Thread.CurrentThread.ManagedThreadId}");
Console.WriteLine($"支付费用... ManagerThreadId={Thread.CurrentThread.ManagedThreadId}");
});


// 会阻塞当前线程,直到任一任务结束
Task.WaitAny(tasks.ToArray());
Console.WriteLine("达到里程碑...");

// 等待上面所有的多线程都计算完毕
// 会阻塞当前线程,直到全部任务结束
Task.WaitAll(tasks.ToArray());


Console.WriteLine("项目上线...");
Console.WriteLine("项目验收...");
Console.WriteLine("支付费用...");

}

多线程安全

如果一段代码,单线程执行和多线程执行结果不一致,就表明由线程安全问题