一、缘起
在上一篇中,我给出了一个简单的CCR应用的例子用来演示CCR不用用户创建管理线程和资源的特性,但是让我高兴的是:有位叫“腊八粥”的朋友,对我给出的程序,做了改进后,发现了2个很奇怪的现象:一个是顺序有时会有点乱,一个是有些数据被丢失了。虽然我在后面的回复里面做了简单解释,但是感觉还是没有将彻底。因为这里涉及到了一个CCR内一个很重要的知识点,也是CCR的组成部分之一:任务调度。
PS:这张可能会有些难懂,因为我本计划把CCR的任务处理策略放到后面来讲的,但是既然有人提到了这个问题,那我觉得顺其自然把这篇文提前贴出来应该是比较好的,大家若能把例子程序改变下策略选项,自己运行下,应该还是能够轻松搞懂的。
OK,那我们就先看看 CCR是如何做任务调度 和 CCR提供的四种任务调度策略,然后再来看看为什么例子程序会出现“腊八粥”说的2个奇怪现象。
二、CCR的任务调度
查阅MSDN:http://msdn.microsoft.com/en-us/library/bb648756.aspx
可知:(下面引用了Ncindy翻译的部分内容,感谢ncindy的辛苦劳动)
【一】:当一个元素被投递到附加了接收器的port,port的实现中将会发生如下操作:
step1. 为投递进来的元素创建一个容器。容器的类型(IPortElement)允许CCR在不知道元素类型的情况下将元素排队并将元素赋值给Task实例。
step2. 容器被放入队列。
step3. 如果接收器列表不是null,并且其中有一个以上的接收器,port对象将会调用ReceiverTask.Evaluate方法来让接收器和它里面的仲裁器层次检测元素是否可以被使用,在这个例子中,Evaluate方法将会返回true,并使用收到的元素和用户的delegate作为参数创建一个Task<int>实例。
step4. port使用调用Evaluate方法返回的Task对象作为参数调用taskQueue.Enqueue,注意,当一个接收器是第一次被激活,它会被关联到由Arbiter.Activate方法提供的DispatcherQueue实例。
当上面的4步完成之后,生成的Task对象现在已经被调度逻辑分发(dealt)给了对应的DispatcherQueue。
【二】:一旦一个元素被放入DispatcherQueue,接下来将会做如下操作:
step1. DispatcherQueue向它所属的Dispatcher发信号,告诉Dispatcher一个新的任务可以被执行了。
step2. Dispatcher通知一个或者多个TaskExecutionWorker类型对象。每个TaskExecutionWorker对象管理一个操作系统线程。它将线程设置到一种高效的休眠状态,直到Dispatcher发出信号通知有元素可以被调度时。
step3. TaskExecutionWorker对象调用DispatcherQueue.Test方法从队列中获取一个任务。如果是可用的任务,TaskExecutionWorker对象则调用ITask.Execute。
step4. Task.Execute方法调用关联在task对象上的delegate,并将一个或者多个关联在task上的参数传递进去。
总之:在CCR中,线程池处理的任务,是由DispatcherQueue产生的;而DispathcerQueue有是根据用户线程通过Port或PortSet提交给的数据 和 初始化时指定的委托来产生任务的。因此可知影响任务调度的地方有3处:
1、客户端提交数据的地方:Port/PortSet的Post方法;
2、DispatcherQueue产生任务的地方:ReceiverTask的Evaluate方法;
3、Dispacher内线程池处理任务的地方:Task执行关联delegate的Execute方法;
而CCR就是通过给上面三处加入调度机制来达到任务调度的负载均衡目的的。
三、CCR的四种任务调度策略
namespace Microsoft.Ccr.Core
{
public enum TaskExecutionPolicy
{
Unconstrained = 0,
ConstrainQueueDepthDiscardTasks = 1,
ConstrainQueueDepthThrottleExecution = 2,
ConstrainSchedulingRateDiscardTasks = 3,
ConstrainSchedulingRateThrottleExecution = 4,
}
}
这4中策略分别应用在一下场景:
1、ConstrainQueueDepthDiscardTasks 按队列深度丢弃最旧任务
适用于:要处理的消息可以丢弃但是必须保存最近N条的情况。这对于CPU处理速度低于消息产生速度的情况很有好处,该策略能够保证丢弃的最旧任务的同时最新的N个任务能都得到调度。特别是在阻塞深度为1的时候,队列中保存的始终都是最新的任务。
2、ConstrainQueueDepthThrottleExecution 按照队列深度阻塞任务产生
适用于:消息不是规律产生,而是随机、爆炸性到达的情况。这对于来自网络获知其他机器的消息很相似,该策略保证任务不会被丢失,通过阻塞消息Post到Port/PortSet的方法来降低任务产生的速度。
3、ConstrainSchedulingRateDiscardTasks 按照固定速度处理消息且丢失未处理的最旧消息
适用于:处理产生速度有规律的消息,比如播放视频。在这种情况下一般所有的消息已经不是最重要的了,但保存最新的消息却很有意义,该策略能够保证代码会以固定的速度执行,即使消息以爆炸式的速度产生也没关系。
4、ConstrainSchedulingRateThrottleExecution 按照固定速度处理消息且阻塞任务缠上
适用于:消息产生源是同一处理器中的另一线程时。该策略会让消息的产生源慢下来,适应消息的处理速度,保证不会有任务丢失。
四、问题&解答
五、附录
1、示范代码:下载
2、本系列其他文章:
我的签名:来自序海的呼唤,期待久违的飞扬
熊掌©原创