假设我有一个云平衡问题的轻微变体,其中Process
不仅有一个权重,而且有一个(正)权重的映射,例如
Map<Long, Long> groupMap = new HashMap<>();
,其中键是特定于我的域,值是权重。
在类Computer
上(仍然参考云平衡示例),我有一个影子变量hist
,这也是一个(Hash)Map<Long, Long>
,和一个自定义侦听器更新hist
:
public class HistListener implements VariableListener {
@Override
public void beforeVariableChanged(ScoreDirector scoreDirector, Object o) {
Process p = (Process) o;
if (p.getComputer() != null) {
Computer kc = p.getComputer();
//update hist Map
scoreDirector.beforeVariableChanged(kc, "hist");
for (Map.Entry<Long, Long> entrySet:k.getGroupMap().entrySet()){
kc.getHist().put(entrySet.getKey(), kc.getHist().get(entrySet.getKey()) - k.getGroupMap().get(entrySet.getKey()));
}
scoreDirector.afterVariableChanged(kc, "hist");
}
}
和afterVariableChanged
差不多,只是符号反了。
我将Process
和Computer
注释为@PlanningEntity
,并将它们注册在solverConfig
中。
没有约束,所以求解器应该能够任意地将computer
s分配给process
e。因此,我希望hist
只具有自然数(包括0)作为值。
当它与<moveThreadCount>NONE</moveThreadCount>
一起运行时,确实是这样:
<"Computer"+computer.id: hist>
Computer0: {0=0, 1=0, 2=20, 3=0, 4=10, 5=20, 6=0, 7=10, 8=10, 9=20}
Computer1: {0=0, 1=10, 2=0, 3=0, 4=10, 5=0, 6=10, 7=0, 8=0, 9=0}
Computer2: {0=0, 1=0, 2=0, 3=0, 4=0, 5=0, 6=0, 7=0, 8=0, 9=0}
当完全运行时与<moveThreadCount>AUTO</moveThreadCount>
相同的代码,我在hist
中部分得到负值:
Computer0: {0=0, 1=-20, 2=30, 3=0, 4=-40, 5=50, 6=-10, 7=30, 8=40, 9=150}
Computer1: {0=0, 1=-40, 2=-20, 3=0, 4=-90, 5=-50, 6=-40, 7=-20, 8=-20, 9=-30}
Computer2: {0=0, 1=80, 2=-20, 3=0, 4=30, 5=-30, 6=50, 7=0, 8=-20, 9=-50}
当我在process
上重构groupMap
的键和在computer
上重构hist
的键作为单独的影子变量时,这种差异就消失了。
trace
日志提示存在竞争条件,即多个线程同时访问hist
。(根据Oracle文档,我只需要一个synchronizedMap
实现,如果映射是结构改变,即,如果键被添加或删除-我不这样做。)
使用Map
作为影子变量极大地增强了我的解决方案的灵活性,如果多线程支持这一点就太好了。我知道我可能会修复这个非常简单的例子与适当的ConstraintProvider
。我的实际问题比这复杂得多,不能用ConstraintProvider
s处理。
问题:是否有可能在多线程上下文中有一个基于Map
的结构作为影子变量?
如果不可能,我建议在optaplanner 8.29.0的文档中添加一个简短的说明。最终版本(我正在使用的版本)。
我看了一下optaplanner中关于List
s作为PlanningVariable
s的问题,但我不知道这些问题与我的问题有什么关系。
是否有可能在多线程上下文中有一个基于Map的结构作为影子变量?
是的,因为多线程环境中的每个move线程内部都有自己的ScoreDirector
和workingSolution
。从影子变量和映射的角度来看,它是单线程的。
什么会把这搞砸呢?
- 在你的数据集中坏的
@PlanningId
,所以Move.rebase()操作出错。id重复或缺少id。OptaPlanner可以检测其中的大部分。这不是你的问题。 - 模型中的克隆计划不完整。可能就是这样。这还会导致在单线程上下文中尚未看到的问题,特别是当终止结束时,最后一个有效的解决方案与最后一个最佳解决方案存在很大差异时。FULL_ASSERT应该检测到这些,但它们可能不会在每次运行时都发生…
每个move线程内部都有自己的workingSolution
。这并不完全正确。他们都有原版的计划克隆版。但是如果计划克隆没有克隆所有受阴影变量影响的数据,那么它就被破坏了。在多线程解决上下文中,这会更快地导致问题。
好了,这变得越来越复杂了。怎么解呢?
尝试在Map
字段上添加@DeepPlanningClone
注释。但是,创建一个影子变量已经意味着要进行深入的计划,以IIRC自动克隆它。我猜映射中的键或值也需要进行规划克隆。请阅读文档中的计划克隆部分。