多线程和并行编程
理解线程、进程、并发的概念
掌握 Thread、线程池、Task 三种多线程方式
掌握 Parallel 并行编程
理解线程安全与 lock 锁
核心讲解
(1)基本概念
进程:运行中的程序,独立资源
线程:CPU 调度的最小单位,一个进程多个线程,共享进程资源
并发:宏观同时,微观切换
并行:真正同时执行,多核 CPU
(2)三种多线程实现
表格
方式 | 特点 | 适用场景 |
|---|---|---|
Thread | 原始线程,控制精细,资源开销大 | 长时间、独立的重量级任务 |
ThreadPool | 线程池,复用线程,开销小 | 大量短任务 |
Task | 推荐方式,基于线程池,功能强,支持异步 / 等待 / 取消 | 绝大多数多线程场景 |
(3)Task 核心用法
创建启动:
Task.Run(() => { })等待完成:
task.Wait()(阻塞)等待多个:
Task.WaitAll()、Task.WaitAny()延续:
task.ContinueWith()
(4)Parallel 并行编程
数据并行,自动分配任务到多个线程,适合循环并行。
Parallel.For():并行 for 循环Parallel.ForEach():并行遍历集合
(5)线程安全
多个线程同时修改共享变量,会出现数据错乱。
解决:
lock锁,保证同一时间只有一个线程进入锁对象:
private static readonly object lockObj = new object();
操作步骤
分别用三种方式创建线程
实现并行循环
验证线程安全问题与 lock 解决
代码示例
using System.Threading;
using System.Threading.Tasks;
// ===== 1. Thread 原始线程 =====
Console.WriteLine("=== Thread ===");
Thread t = new Thread(() =>
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"线程1:{i}");
Thread.Sleep(200);
}
});
t.Start();
// ===== 2. Task(推荐) =====
Console.WriteLine("\n=== Task ===");
Task task1 = Task.Run(() =>
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"任务1:{i}");
Thread.Sleep(200);
}
});
Task<int> task2 = Task.Run(() =>
{
int sum = 0;
for (int i = 1; i <= 100; i++) sum += i;
return sum;
});
Console.WriteLine("任务2结果:" + task2.Result); // 阻塞等待结果
// ===== 3. Parallel 并行循环 =====
Console.WriteLine("\n=== Parallel ===");
Parallel.For(0, 10, i =>
{
Console.WriteLine($"并行{i},线程ID:{Thread.CurrentThread.ManagedThreadId}");
});
// ===== 4. 线程安全与 lock =====
Console.WriteLine("\n=== 线程安全 ===");
int count = 0;
object lockObj = new object();
Parallel.For(0, 1000, i =>
{
lock (lockObj) // 加锁,保证原子操作
{
count++;
}
});
Console.WriteLine("最终计数:" + count); // 加锁后稳定1000
Console.ReadKey();
常见误区
多线程操作界面控件:跨线程不能直接操作 UI 控件,要用 Invoke 调度到 UI 线程
lock 锁字符串 /this:锁必须是私有静态对象,锁字符串会被公共池复用,锁 this 不安全
死锁:嵌套锁、顺序不一致会导致死锁,尽量减少锁的范围
线程越多越快:线程太多会频繁切换,反而降低性能,一般和 CPU 核心数相当
课后练习
用 Task 启动两个任务,分别计算 1-5000 和 5001-10000 的和
用 Parallel 遍历一个大数组,每个元素乘 2
实现一个计数器,不加锁观察错误,加锁后验证正确
对比 Thread 和 Task,为什么推荐优先用 Task?

