Multithreading and generic callbacks

Generic callbacks demand certain considerations when used in a multithreaded application.

There are conventions to observe when you use a generic callback in a multithreaded application of CPLEX.

Global and thread-local information

In general, any information that can be queried in a generic callback is thread-local. That is, the values returned for queries are based on the information that the current thread can see at the moment the callback is invoked. This return is not necessarily all information available to CPLEX.

Consider, for example, a multithreaded search of a tree in a minimization problem. Let T denote the set of all open nodes. Assume that the tree is searched with two threads, and T is partitioned into three sets: T1, T2, and TX. Let the nodes in T1 be assigned to the first thread, and those in T2 to the second thread. Nodes in TX are not assigned to any thread. In this setting, best objective bound in the first thread is the minimum of the LP relaxations of all nodes in T1. This bound may well be larger than the global objective bound, which is the minimum of the LP relaxations of all nodes in T1, T2, and TX. Moreover, the objective bound in the first thread can decrease if a node that is currently in TX moves to T1. Similar considerations apply to the best known objective value.

The only situation in which callback queries return globally valid data is when the context of the generic callback is CPX_CALLBACKCONTEXT_GLOBAL_PROGRESS. This context is also the only case in which CPLEX guarantees a deterministic order of callback invocations. For all other callback contexts, the order of callback invocations between different threads is not deterministic.

Speculative execution of code

In general, you cannot assume that information that you pass to CPLEX from within a generic callback will be used and recorded by CPLEX.

In multithreaded execution, CPLEX may perform speculative execution of some algorithms. A common case is that CPLEX launches search tasks that are parallel to the main solution algorithm. These tasks might be interrupted before their completion or even possibly ignored. This situation means that anything (such as a heuristic, a lazy constraint, and so forth) that was passed by the user within these tasks through the generic callback might be discarded and might not be applied as the user expects.

Likewise, any information found by CPLEX itself (and reported to the user) in such tasks might have to be discarded. Consequently, you can not assume that any thread-local information reported from a generic callback will definitely make it into a globally valid state. Only global information is definitive.

In contrast, CPLEX does not invoke a generic callback in the context CPX_CALLBACKCONTEXT_GLOBAL_PROGRESS when CPLEX is engaged in speculative evaluation. Thus, everything submitted in such an invocation can be used by CPLEX.

Thread safety and generic callbacks

CPLEX itself cannot guarantee the thread-safety of a generic callback in an application. In other words, thread-safety in a multithreaded application is ultimately the responsibility of the user. You must protect access to global data, for example, by appropriate locks. However, if CPLEX routines accept CPXCALLBACKCONTEXTptr as an argument, then you can safely call those routines from generic callbacks in different threads.

Tip: There is an important exception to that general rule: invocations of a generic callback in the context CPX_CALLBACKCONTEXT_GLOBAL_PROGRESS holds a lock. Such invocations are consequently thread-safe. However, lengthy computations in the callback blocks other threads. In other words, your application should not perform lengthy computations in such situations because you risk blocking other threads.

Thread notifications

It may be important to your application to know when CPLEX starts or ends a new thread. In other words, you want CPLEX to notify your application when CPLEX starts or ends a new thread. In such a case, have CPLEX invoke the callback with the context CPX_CALLBACKCONTEXT_THREAD_UP when it starts the thread. Likewise, have CPLEX invoke the callback with the context CPX_CALLBACKCONTEXT_THREAD_DOWN when it ends the thread. These contexts carry out "book-keeping" tasks. For example, CPX_CALLBACKCONTEXT_THREAD_UP can potentially create thread-local data, while CPX_CALLBACKCONTEXT_THREAD_DOWN can potentially destroy thread-local data.

These contexts typically have no information about the current state or progress of the search. Attempts to query such information from these contexts returns an error.

Furthermore, CPX_CALLBACKCONTEXT_THREAD_DOWN ignores the return value of the callback function.