Skip to content

线程

约 1569 字大约 5 分钟

线程

2024-03-16

  • Thread
  • ThreadPool:特点 复用
  • Task:线程池的封装
  • BeginInvoke

task是线程池的封装

单核下没有多线程

线程优先级反转 由于线程是栈 先进后出

eg:A 优先级最高 ,B其次,C最低

当C占用资源时 AB 都想要拿到资源时,A先进栈 然后B 但是他们都拿不到资源 A还进入栈的最下面,所以会变成A的优先级最低 这时可以执行Task.Delay(0).Wait(); || Thread.sleep(0) 暂停0秒 也会执行将栈的下一个拿出来 所以又会变成优先级最高

异步引发并发,但不一定会出现并发,

net中分前台线程与后台线程 区别:是否让创建的所有线程都退出

  • 前台线程
    • 当主方法结束时,前台线程后等待
  • 后台线程不会等待

说人话就是退出程序时,前台线程会阻止进程退出,后台线程会跟随程序一起销毁退出

程序(进程)会等到所有前台线程都跑完才真正退出;

后台线程跑不跑完无所谓,一旦所有前台线程结束了,后台线程会被强制终止(不等它执行完)。

new Thread(()=>{
	
})
{ IsBackgroud  = true }.Start();

ExecutionContext

如何做到在多线程中共享数据(队列、线程、工作项、await、async,task)之类的东西

NET是通过 ExecutionContext 执行上下文

执行上下文:把所有线程的本地状态放到其中,并随着所有的线程异步一起流转 理解为多元宇宙,携带同样的参数在不同的线程中穿行

本质上是一个花哨的键值对

Sleep 与 Delay 的区别

Thread.Sleep(1000); await Task.Delay(1000);

**Sleep是阻塞当前线程 会一直占用这个线程。Delay是释放当前线程回线程池,暂停执行并且交还控制权,等待时间到了再去请求线程继续执行后面的代码

Sleep让线程睡眠(等待停止),Delay内部是计时器实现的

 async static Task Main(string[] args)
 {

     var tasks = new List<Task>();

     for (int i = 0; i < 5; i++)
     {
         int idx = i;
         tasks.Add(Task.Run(async () =>   // 注意这里可以是 async lambda
         {
             Console.WriteLine($"任务 {idx} 开始");

             if (idx == 3)
             {
                 Console.WriteLine($"线程ID:{Thread.CurrentThread.ManagedThreadId}   → 睡 10 秒");
                 await Task.Delay(10000);   // ← 异步,不卡线程
                 //Thread.Sleep(10000);   // ← 睡眠阻塞,卡线程
                 Console.WriteLine($"线程ID:{Thread.CurrentThread.ManagedThreadId}   → 睡醒干活");
             }
             else
             {
                 await Task.Delay(500);
                 //Thread.Sleep(500);
             }
             Console.WriteLine($"任务 {idx} 完成");
         }));
     }

     Console.WriteLine("主线程(异步)等待中...");
     await Task.WhenAll(tasks);   // ← 不阻塞线程池
     Console.WriteLine("全部完成");
}

Delay输出:

17:00:16.480 主线程(异步)等待中...
17:00:16.490 任务 2 开始
17:00:16.490 任务 4 开始
17:00:16.490 任务 3 开始
17:00:16.490 任务 1 开始
17:00:16.490 任务 0 开始
17:00:16.504 线程ID:9 10
17:00:17.020 任务 1 完成
17:00:17.020 任务 4 完成
17:00:17.020 任务 0 完成
17:00:17.020 任务 2 完成
17:00:26.512 线程ID:9 睡醒干活
17:00:26.512 任务 3 完成
17:00:26.512 全部完成

C:\Users\yuche\source\repos\MyWpfApps\DeepNet\bin\Debug\net8.0\DeepNet.exe (进程 9500)已退出,代码为 0 (0x0)。

Sleep输出:

16:58:43.324 任务 3 开始
16:58:43.315 主线程(异步)等待中...
16:58:43.324 任务 1 开始
16:58:43.324 任务 0 开始
16:58:43.324 任务 2 开始
16:58:43.324 任务 4 开始
16:58:43.337 线程ID:10 10
16:58:43.840 任务 0 完成
16:58:43.840 任务 1 完成
16:58:43.840 任务 2 完成
16:58:43.840 任务 4 完成
16:58:53.341 线程ID:10 睡醒干活
16:58:53.343 任务 3 完成
16:58:53.346 全部完成

C:\Users\yuche\source\repos\MyWpfApps\DeepNet\bin\Debug\net8.0\DeepNet.exe (进程 14056)已退出,代码为 0 (0x0)。

相关信息

明显可以看出delay后继续执行的线程变了,而sleep的线程没变

线程安全集合类

类/结构主要特点性能/开销考虑
BlockingCollection<T>- 阻塞式添加/取出(空/满时等待)。
- 支持有界(bounded)容量。 - CompleteAdding 机制结束生产。
- 默认底层是 ConcurrentQueue,可自定义(如用 ConcurrentStack)。
- 同步阻塞,适合简单同步场景。
中等(阻塞引入上下文切换开销,但线程安全)。
ConcurrentQueue<T>- 无界队列,FIFO。
- 非阻塞:TryEnqueue/TryDequeue(失败返回false)。
- 高性能,锁-free。
- 无 CompleteAdding,无内置阻塞。
高(锁-free,适合高吞吐)。
ConcurrentStack<T>- 无界栈,LIFO。 - 非阻塞:TryPush/TryPop。
- 类似 Queue,但后进先出。
高(锁-free)。
ConcurrentBag<T>- 无界、无序集合。 - 非阻塞:Add/TryTake。
- 适合并行添加/取出,无特定顺序。
高(工作窃取优化,适合并行)。
Channel<T> (.NET 5+)- 异步通道,支持有界/无界。
- 阻塞用 WaitToReadAsync/WaitToWriteAsync + TryRead/TryWrite。 - 支持 Complete/Reader.Completion。
- 内置异步,适合 async/await。
高(异步,低开销;无锁在单生产者/消费者)。
BufferBlock<T> (TPL Dataflow)- 数据流块,支持有界。
- 阻塞 Post/OfferMessage;异步 ReceiveAsync。 - 可链接其他块(如 TransformBlock),形成管道。
- 支持 Completion/衰退模式。
中等(TPL 调度器开销)。
Queue<T> 或 Stack<T>- 非线程安全,需手动锁
- 不推荐多线程使用

FIFO(Fitst In First Out) :先进先出 LIFO(Last In First Out):后尽先出

使用场景

  • 简单同步生产者-消费者,需阻塞等待(后台任务填充队列,前台消费)
    • 首选:BlockingCollection<T>。易用,内置阻塞和 CompleteAdding。示例:日志队列,生产者添加日志,消费者写入文件。
  • 高性能、无阻塞队列(消息传递,不想阻塞线程)
    • 首选:ConcurrentQueue<T>。用 Try* 方法 + 轮询或信号量。示例:游戏循环中的事件队列,高吞吐无需等。
  • 异步/await 场景(Web API 或现代 app)
    • 首选:Channel<T>。支持 async ReadAsync/WriteAsync,避免阻塞线程。示例:SignalR 实时消息,或流式处理数据。比 BlockingCollection 更高效(无线程池阻塞)。
  • 复杂数据管道(ETL 处理:输入 → 转换 → 输出)
    • 首选:BufferBlock<T> + TPL Dataflow。可链式链接块,支持并行/衰退。示例:图像处理管道,多个步骤并发。
  • 无序/并行添加(多线程收集结果,无需顺序)
    • 首选:ConcurrentBag<T>。示例:并行 foreach 收集数据。
  • LIFO 场景(撤销栈)
    • 首选:ConcurrentStack<T>,或用它包装 BlockingCollection。