聊一聊 C# 线程切换后上下文都去了哪里

一:背景

1. 讲故事

总会有一些朋友问一个问题,在 Windows 中线程做了上下文切换,请问被切的线程他的寄存器上下文都去了哪里?能不能给我挖出来?这个问题其实比较底层,如果对操作系统没有个体系层面的理解以及做过源码分析,其实很难说明白,这篇我们就从.NET高级调试的角度试着分析一下吧。

二:寄存器上下文去哪了

1. 用户线程的两态空间

用C#代码创建的线程在操作系统层面上来说属于 用户态线程,这种线程拥有两个线程栈,哈哈,是不是打破了一些朋友的三观。分别为 用户态栈 和 内核态栈

为了方便讲解,写一段简单的测试代码,不断的调用 Sleep(1) 让代码在用户态和内核态不断的切换,也就能观察得到这两套栈空间,参考代码如下:


        static void Main(string[] args)
        {
            for (int i = 0; i < int.MaxValue; i++)
            {
                Thread.Sleep(1);
                Console.WriteLine($"i={i}");
            }
        }

将程序跑起来后我们用 windbg

为了减少C#应用程序中长时间运行的线程之间的上下文切换,可以采用些策略优化程序设计和资源管理: ### 使用`ThreadPriority` 通过降低非关键任务线程的优先级可以让更重要的线程获得更多的CPU时间,但这并不是直接解决上下文切换频率的问题。 ```csharp var workerThread = new Thread(new ThreadStart(DoWork)); workerThread.Priority = ThreadPriority.BelowNormal; ``` 然而,在现代操作系统上手动调整线程优先级并不总是推荐的做法,因为操作系统的调度器通常已经做得很好了,并且改变优先级可能会导致其他意想不到的行为如饥饿现象(某些低优先级线程无法得到足够的处理机会)。 ### 减少临界区 确保最小化同步原语(例如Monitor、Mutex等锁机制)作用范围内的代码量。过多地锁定会增加等待获取锁的时间以及潜在的竞争条件,这实际上增加了不必要的上下文切换次数。 对于共享数据的操作尽可能短暂并且只对必要的部分加锁: ```csharp lock (syncRoot) { // 尽可能短小的关键区域 } ``` 考虑使用更高效的并发集合(`ConcurrentQueue<T>`, `ConcurrentDictionary<TKey,TValue>` 等),它们内部实现了无锁算法或者减少了争用情况的发生几率。 ### 限制并行度 如果你的应用场景允许的话,将工作项划分成更大的批处理单元来减少线程池中的活动线程数目也可以有效缓解这个问题;另外就是避免创建比实际需要更多数量的工作线程,比如利用TPL Dataflow库构建管道式的数据流网络。 当确实需要用到固定大小的长期存在的后台工作者时,则应该设置合理的最大值以防止过度占用系统资源而引发频繁的上下文转换开销。 最后别忘了配置适当的任务计划选项(TaskScheduler) 和 I/O完成端口(IOCPs) 来保证异步I/O操作不会阻塞主线程或引起额外负担。 综上所述,虽然不能完全消除上下文切换所带来的性能损失,但是合理规划应用架构及选择合适的工具和技术能够显著改善整体效率。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值