多线程和类属回调
类属回调在用于多线程应用程序中时需要注意一些特定事项。
在 CPLEX 的多线程应用程序中使用类属回调时,有些约定要予以遵守。
全局和线程本地信息
通常,可以在类属回调中查询的任何信息都是线程本地信息。 即,针对查询返回的值基于当前线程在调用回调时可以看到的信息。 此返回未必是对于 CPLEX 可用的所有信息。
例如,考虑在最小化问题中对树执行多线程搜索。 让 T 表示所有开放节点集。 假设使用两个线程对该树进行搜索,并且 T 分区成三个集:T1、T2 和 TX。 让 T1 中的节点分配到第一个线程,并让 T2 中的节点分配到第二个线程。 TX 中的节点未分配到任何线程。 在此设置中,第一个线程中的最佳目标边界是 T1 中所有节点的 LP 松弛的最小值。 此边界很可能大于全局目标边界(即,T1、T2 和 TX 中所有节点的 LP 松弛的最小值)。 此外,如果当前位于 TX 中的节点移至 T1,那么第一个线程中的目标边界可能会减小。 类似的注意事项适用于最佳已知目标值。
回调查询返回全局有效数据的唯一情况是通用回调的上下文为CPX_CALLBACKCONTEXT_GLOBAL_PROGRESS。 此上下文也是 CPLEX 保证回调调用的确定性顺序的唯一情况。 对于所有其他上下文,不同线程之间的回调调用的顺序不是确定性的。
代码的推测执行
通常,不能假定从类属回调内传递到 CPLEX 的信息将由 CPLEX 使用和记录。
在多线程执行中,CPLEX 可以对一些算法执行推测执行。 一个常见情况是 CPLEX 启动与主要求解算法并行的搜索任务。 这些任务可能会在完成之前中断,甚至可能被忽略。 此情况意味着用户通过类属回调在这些任务中传递的任何内容(例如启发式回调、惰性约束等)可能会废弃,并且可能不会按用户预期进行应用。
同样,可能必须废弃在此类任务中由 CPLEX 本身找到(以及向用户报告)的任何信息。 因此,不能假定从类属回调报告的任何线程本地信息都将确定使其处于全局有效状态。 仅全局信息为确定性。
相比之下,CPLEX 在进行推测性评估时不会调用CPX_CALLBACKCONTEXT_GLOBAL_PROGRESS上下文中的通用回调。 因此,在此类调用中提交的所有内容都可由 CPLEX 使用。
线程安全和类属回调
CPLEX 本身无法保证应用程序中类属回调的线程安全。 换言之,多线程应用程序中的线程安全最终由用户负责。 必须保护对全局数据的访问(例如,通过适当的锁定)。 不过,如果 CPLEX 例程接受CPXCALLBACKCONTEXTptr作为参数,则可以在不同线程中安全地通过通用回调调用这些例程。
线程通知
应用程序知道 CPLEX 何时启动或结束新线程非常重要。 换言之,您希望 CPLEX 在启动或结束新线程时对应用程序进行通知。 在这种情况下,CPLEX 在启动线程时会调用上下文CPX_CALLBACKCONTEXT_THREAD_UP。 同样,CPLEX 在结束线程时也会调用上下文CPX_CALLBACKCONTEXT_THREAD_DOWN。 这些上下文执行“簿记”任务。 例如,"cpx_callbackcontext_thread_up有可能创建线程本地数据,而 "cpx_callbackcontext_thread_down有可能销毁线程本地数据。
这些上下文通常没有关于搜索的当前状态或进度的任何信息。 尝试从这些上下文查询此类信息会返回错误。
此外,CPX_CALLBACKCONTEXT_THREAD_DOWN会忽略回调函数的返回值。